File indexing completed on 2024-05-12 05:21:21

0001 /*
0002   This file is part of KOrganizer.
0003 
0004   SPDX-FileCopyrightText: 1997, 1998, 1999 Preston Brown <preston.brown@yale.edu>
0005   SPDX-FileCopyrightText: Fester Zigterman <F.J.F.ZigtermanRustenburg@student.utwente.nl>
0006   SPDX-FileCopyrightText: Ian Dawes <iadawes@globalserve.net>
0007   SPDX-FileCopyrightText: Laszlo Boloni <boloni@cs.purdue.edu>
0008 
0009   SPDX-FileCopyrightText: 2000-2004 Cornelius Schumacher <schumacher@kde.org>
0010   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0011   SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
0012 
0013   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0014 */
0015 
0016 #include "calendarview.h"
0017 
0018 #include "akonadicollectionview.h"
0019 #include "collectiongeneralpage.h"
0020 #include "datechecker.h"
0021 #include "datenavigator.h"
0022 #include "datenavigatorcontainer.h"
0023 #include "dialog/koeventviewerdialog.h"
0024 #include "kodaymatrix.h"
0025 #include "kodialogmanager.h"
0026 #include "koglobals.h"
0027 #include "koviewmanager.h"
0028 #include "pimmessagebox.h"
0029 #include "prefs/koprefs.h"
0030 #include "views/agendaview/koagendaview.h"
0031 #include "views/monthview/monthview.h"
0032 #include "views/todoview/kotodoview.h"
0033 #include "widgets/navigatorbar.h"
0034 
0035 #include <Akonadi/AttributeFactory>
0036 #include <Akonadi/CollectionIdentificationAttribute>
0037 
0038 #include <Akonadi/CalendarClipboard>
0039 #include <Akonadi/CalendarUtils>
0040 #include <Akonadi/FreeBusyManager>
0041 #include <Akonadi/IncidenceChanger>
0042 #include <Akonadi/TodoPurger>
0043 #include <akonadi/calendarsettings.h> //krazy:exclude=camelcase this is a generated file
0044 
0045 #include <Akonadi/CollectionMaintenancePage>
0046 #include <Akonadi/CollectionPropertiesDialog>
0047 #include <Akonadi/ControlGui>
0048 
0049 #include <CalendarSupport/CalPrinter>
0050 #include <CalendarSupport/CalendarSingleton>
0051 #include <CalendarSupport/IncidenceViewer>
0052 #include <CalendarSupport/KCalPrefs>
0053 #include <CalendarSupport/Utils>
0054 
0055 #include <IncidenceEditor/IncidenceDefaults>
0056 #include <IncidenceEditor/IncidenceDialog>
0057 #include <IncidenceEditor/IncidenceDialogFactory>
0058 #include <IncidenceEditor/IncidenceEditorSettings>
0059 #include <IncidenceEditor/IndividualMailComponentFactory>
0060 
0061 #include <KCalendarCore/CalFilter>
0062 #include <KCalendarCore/FileStorage>
0063 #include <KCalendarCore/ICalFormat>
0064 
0065 #include <KCalUtils/DndFactory>
0066 #include <KCalUtils/Stringify>
0067 
0068 #include <PimCommonAkonadi/CollectionAclPage>
0069 #include <PimCommonAkonadi/ImapAclAttribute>
0070 
0071 #include <KDialogJobUiDelegate>
0072 #include <KIO/CommandLauncherJob>
0073 #include <KMessageBox>
0074 
0075 #include <QApplication>
0076 #include <QClipboard>
0077 #include <QFileDialog>
0078 #include <QSplitter>
0079 #include <QStackedWidget>
0080 #include <QVBoxLayout>
0081 
0082 // Meaningful aliases for dialog box return codes.
0083 enum ItemActions {
0084     Cancel = KMessageBox::Cancel, // Do nothing.
0085     Current = KMessageBox::Ok, // Selected recurrence only.
0086     AlsoFuture = KMessageBox::ButtonCode::SecondaryAction, // Selected and future recurrences.
0087     Parent = KMessageBox::ButtonCode::PrimaryAction, // Instance, but not child instances.
0088     All = KMessageBox::Continue, // Instance and child instances.
0089 };
0090 
0091 CalendarView::CalendarView(QWidget *parent)
0092     : CalendarViewBase(parent)
0093     , mSearchCollectionHelper(this)
0094 {
0095     Akonadi::ControlGui::widgetNeedsAkonadi(this);
0096     mChanger = new Akonadi::IncidenceChanger(new IncidenceEditorNG::IndividualMailComponentFactory(this), this);
0097     mChanger->setDefaultCollection(Akonadi::Collection(CalendarSupport::KCalPrefs::instance()->defaultCalendarId()));
0098 
0099     mChanger->setDestinationPolicy(static_cast<Akonadi::IncidenceChanger::DestinationPolicy>(KOPrefs ::instance()->destination()));
0100 
0101     // We reuse the EntityTreeModel from the calendar singleton to save memory.
0102     // We don't reuse the entire ETMCalendar because we want a different selection model. Checking/unchecking
0103     // calendars in korganizer shouldn't affect kontact's summary view
0104     mCalendar = Akonadi::ETMCalendar::Ptr(new Akonadi::ETMCalendar(CalendarSupport::calendarSingleton().data()));
0105 
0106     mCalendar->setObjectName(QLatin1StringView("KOrg Calendar"));
0107     mCalendarClipboard = new Akonadi::CalendarClipboard(mCalendar, mChanger, this);
0108     mITIPHandler = new Akonadi::ITIPHandler(this);
0109     mITIPHandler->setCalendar(mCalendar);
0110     connect(mCalendarClipboard, &Akonadi::CalendarClipboard::cutFinished, this, &CalendarView::onCutFinished);
0111 
0112     mChanger->setEntityTreeModel(mCalendar->entityTreeModel());
0113 
0114     Akonadi::AttributeFactory::registerAttribute<PimCommon::ImapAclAttribute>();
0115     Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionIdentificationAttribute>();
0116 
0117     mViewManager = new KOViewManager(this);
0118     mDialogManager = new KODialogManager(this);
0119     mTodoPurger = new Akonadi::TodoPurger(this);
0120     mTodoPurger->setCalendar(mCalendar);
0121     mTodoPurger->setIncidenceChager(mChanger);
0122     connect(mTodoPurger, &Akonadi::TodoPurger::todosPurged, this, &CalendarView::onTodosPurged);
0123 
0124     mReadOnly = false;
0125     mSplitterSizesValid = false;
0126 
0127     mCalPrinter = nullptr;
0128 
0129     mDateNavigator = new DateNavigator(this);
0130     mDateChecker = new DateChecker(this);
0131 
0132     auto topLayout = new QVBoxLayout(this);
0133     topLayout->setContentsMargins({});
0134 
0135     // create the main layout frames.
0136     mPanner = new QSplitter(Qt::Horizontal, this);
0137     mPanner->setObjectName(QLatin1StringView("CalendarView::Panner"));
0138     topLayout->addWidget(mPanner);
0139 
0140     mLeftSplitter = new QSplitter(Qt::Vertical, mPanner);
0141     mLeftSplitter->setObjectName(QLatin1StringView("CalendarView::LeftFrame"));
0142     // The GUI checkboxes of "show widget XYZ" are confusing when the QSplitter
0143     // hides the widget magically. I know I blamed Akonadi for not showing my
0144     // calendar more than once.
0145     mLeftSplitter->setChildrenCollapsible(false);
0146 
0147     mDateNavigatorContainer = new DateNavigatorContainer(mLeftSplitter);
0148     mDateNavigatorContainer->setObjectName(QLatin1StringView("CalendarView::DateNavigator"));
0149 
0150     mTodoList = new KOTodoView(true /*sidebar*/, mLeftSplitter);
0151     mTodoList->setObjectName(QLatin1StringView("todolist"));
0152     connect(this, &CalendarView::calendarAdded, mTodoList, &KOTodoView::calendarAdded);
0153     connect(this, &CalendarView::calendarRemoved, mTodoList, &KOTodoView::calendarRemoved);
0154 
0155     mEventViewerBox = new QWidget(mLeftSplitter);
0156     auto mEventViewerBoxVBoxLayout = new QVBoxLayout(mEventViewerBox);
0157     mEventViewerBoxVBoxLayout->setContentsMargins({});
0158     mEventViewerBoxVBoxLayout->setContentsMargins({});
0159     mEventViewer = new CalendarSupport::IncidenceViewer(mCalendar->entityTreeModel(), mEventViewerBox);
0160     mEventViewer->setObjectName(QLatin1StringView("EventViewer"));
0161     mEventViewerBoxVBoxLayout->addWidget(mEventViewer);
0162 
0163     auto rightBox = new QWidget(mPanner);
0164     auto rightBoxVBoxLayout = new QVBoxLayout(rightBox);
0165     rightBoxVBoxLayout->setContentsMargins({});
0166     mNavigatorBar = new NavigatorBar(rightBox);
0167     rightBoxVBoxLayout->addWidget(mNavigatorBar);
0168     mRightFrame = new QStackedWidget(rightBox);
0169     rightBoxVBoxLayout->addWidget(mRightFrame);
0170     mMessageWidget = new CalendarSupport::MessageWidget(rightBox);
0171     rightBoxVBoxLayout->addWidget(mMessageWidget);
0172 
0173     rightBoxVBoxLayout->setStretchFactor(mRightFrame, 1);
0174 
0175     mLeftFrame = mLeftSplitter;
0176     mLeftFrame->installEventFilter(this);
0177 
0178     mChanger->setGroupwareCommunication(CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication());
0179     connect(mChanger, &Akonadi::IncidenceChanger::createFinished, this, &CalendarView::slotCreateFinished);
0180 
0181     connect(mChanger, &Akonadi::IncidenceChanger::deleteFinished, this, &CalendarView::slotDeleteFinished);
0182 
0183     connect(mChanger, &Akonadi::IncidenceChanger::modifyFinished, this, &CalendarView::slotModifyFinished);
0184 
0185     // Signals emitted by mDateNavigator
0186     connect(mDateNavigator, &DateNavigator::datesSelected, this, &CalendarView::showDates);
0187 
0188     connect(mDateNavigatorContainer, &DateNavigatorContainer::newEventSignal, this, [this](const QDate &s, const QDate &t) {
0189         newEvent(QDateTime(s, QTime::currentTime()), QDateTime(t, QTime::currentTime()));
0190     });
0191 
0192     connect(mDateNavigatorContainer, &DateNavigatorContainer::newTodoSignal, this, qOverload<const QDate &>(&CalendarView::newTodo));
0193     connect(mDateNavigatorContainer, &DateNavigatorContainer::newJournalSignal, this, qOverload<const QDate &>(&CalendarView::newJournal));
0194 
0195     // Signals emitted by mNavigatorBar
0196     connect(mNavigatorBar, &NavigatorBar::prevYearClicked, mDateNavigator, &DateNavigator::selectPreviousYear);
0197     connect(mNavigatorBar, &NavigatorBar::nextYearClicked, mDateNavigator, &DateNavigator::selectNextYear);
0198     connect(mNavigatorBar, &NavigatorBar::prevMonthClicked, this, [this]() {
0199         mDateNavigator->selectPreviousMonth();
0200     });
0201     connect(mNavigatorBar, &NavigatorBar::nextMonthClicked, this, [this]() {
0202         mDateNavigator->selectNextMonth();
0203     });
0204     connect(mNavigatorBar, &NavigatorBar::monthSelected, mDateNavigator, &DateNavigator::selectMonth);
0205     connect(mNavigatorBar, &NavigatorBar::yearSelected, mDateNavigator, &DateNavigator::selectYear);
0206 
0207     // Signals emitted by mDateNavigatorContainer
0208     connect(mDateNavigatorContainer, &DateNavigatorContainer::weekClicked, this, &CalendarView::selectWeek);
0209 
0210     connect(mDateNavigatorContainer, &DateNavigatorContainer::prevMonthClicked, mDateNavigator, &DateNavigator::selectPreviousMonth);
0211     connect(mDateNavigatorContainer, &DateNavigatorContainer::nextMonthClicked, mDateNavigator, &DateNavigator::selectNextMonth);
0212     connect(mDateNavigatorContainer, &DateNavigatorContainer::prevYearClicked, mDateNavigator, &DateNavigator::selectPreviousYear);
0213     connect(mDateNavigatorContainer, &DateNavigatorContainer::nextYearClicked, mDateNavigator, &DateNavigator::selectNextYear);
0214     connect(mDateNavigatorContainer, &DateNavigatorContainer::monthSelected, mDateNavigator, &DateNavigator::selectMonth);
0215     connect(mDateNavigatorContainer, &DateNavigatorContainer::yearSelected, mDateNavigator, &DateNavigator::selectYear);
0216     connect(mDateNavigatorContainer, &DateNavigatorContainer::goPrevious, mDateNavigator, &DateNavigator::selectPrevious);
0217     connect(mDateNavigatorContainer, &DateNavigatorContainer::goNext, mDateNavigator, &DateNavigator::selectNext);
0218 
0219     connect(mDateNavigatorContainer,
0220             &DateNavigatorContainer::datesSelected,
0221             mDateNavigator,
0222             qOverload<const KCalendarCore::DateList &, QDate>(&DateNavigator::selectDates));
0223 
0224     connect(mViewManager, &KOViewManager::datesSelected, mDateNavigator, [this](const KCalendarCore::DateList &dates) {
0225         mDateNavigator->selectDates(dates);
0226     });
0227 
0228     connect(mDateNavigatorContainer, &DateNavigatorContainer::incidenceDropped, this, &CalendarView::addIncidenceOn);
0229     connect(mDateNavigatorContainer, &DateNavigatorContainer::incidenceDroppedMove, this, &CalendarView::moveIncidenceTo);
0230 
0231     connect(mDateChecker, &DateChecker::dayPassed, mTodoList, &BaseView::dayPassed);
0232     connect(mDateChecker, &DateChecker::dayPassed, this, &CalendarView::dayPassed);
0233     connect(mDateChecker, &DateChecker::dayPassed, mDateNavigatorContainer, &DateNavigatorContainer::updateToday);
0234 
0235     connect(this, &CalendarView::configChanged, mDateNavigatorContainer, &DateNavigatorContainer::updateConfig);
0236 
0237     connect(this, &CalendarView::incidenceSelected, mEventViewer, &CalendarSupport::IncidenceViewer::setIncidence);
0238 
0239     // TODO: do a pretty Summary,
0240     const QString s = i18n(
0241         "<p><em>No Item Selected</em></p>"
0242         "<p>Select an event, to-do or journal entry to view its details "
0243         "here.</p>");
0244 
0245     mEventViewer->setDefaultMessage(s);
0246     mEventViewer->setWhatsThis(
0247         i18n("View the details of events, journal entries or to-dos "
0248              "selected in KOrganizer's main view here."));
0249     mEventViewer->setIncidence(Akonadi::Item(), QDate());
0250 
0251     mViewManager->connectTodoView(mTodoList);
0252     mViewManager->connectView(mTodoList);
0253 
0254     KOGlobals::self()->setHolidays(KOPrefs::instance()->mHolidays);
0255 
0256     connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &CalendarView::checkClipboard);
0257 
0258     connect(mTodoList, &BaseView::incidenceSelected, this, &CalendarView::processTodoListSelection);
0259     disconnect(mTodoList, &BaseView::incidenceSelected, this, &CalendarView::processMainViewSelection);
0260 
0261     {
0262         static bool pageRegistered = false;
0263 
0264         if (!pageRegistered) {
0265             Akonadi::CollectionPropertiesDialog::registerPage(new CalendarSupport::CollectionGeneralPageFactory);
0266             Akonadi::CollectionPropertiesDialog::registerPage(new PimCommon::CollectionAclPageFactory);
0267             Akonadi::CollectionPropertiesDialog::registerPage(new Akonadi::CollectionMaintenancePageFactory);
0268             pageRegistered = true;
0269         }
0270     }
0271 
0272     Akonadi::FreeBusyManager::self()->setCalendar(mCalendar);
0273 
0274     mCalendar->registerObserver(this);
0275 }
0276 
0277 CalendarView::~CalendarView()
0278 {
0279     mCalendar->unregisterObserver(this);
0280     mCalendar->setFilter(nullptr); // So calendar doesn't deleted it twice
0281     qDeleteAll(mFilters);
0282     qDeleteAll(mExtensions);
0283 
0284     delete mDialogManager;
0285     delete mViewManager;
0286     delete mEventViewer;
0287 }
0288 
0289 Akonadi::ETMCalendar::Ptr CalendarView::calendar() const
0290 {
0291     return mCalendar;
0292 }
0293 
0294 QList<Akonadi::CollectionCalendar::Ptr> CalendarView::enabledCalendars() const
0295 {
0296     return mEnabledCalendars;
0297 }
0298 
0299 Akonadi::CollectionCalendar::Ptr CalendarView::calendarForCollection(const Akonadi::Collection &collection)
0300 {
0301     auto it = mCalendars.begin();
0302     while (it != mCalendars.end()) {
0303         if (it->isNull()) {
0304             it = mCalendars.erase(it);
0305             continue;
0306         }
0307 
0308         const auto calendar = it->toStrongRef();
0309         if (calendar->collection() == collection) {
0310             return calendar;
0311         }
0312 
0313         ++it;
0314     }
0315 
0316     const auto calendar = Akonadi::CollectionCalendar::Ptr::create(mCalendar->entityTreeModel(), collection);
0317     mCalendars.emplace_back(calendar);
0318     return calendar;
0319 }
0320 
0321 QDate CalendarView::activeDate(bool fallbackToToday)
0322 {
0323     KOrg::BaseView *curView = mViewManager->currentView();
0324     if (curView) {
0325         if (curView->selectionStart().isValid()) {
0326             return curView->selectionStart().date();
0327         }
0328 
0329         // Try the view's selectedIncidenceDates()
0330         if (!curView->selectedIncidenceDates().isEmpty()) {
0331             if (curView->selectedIncidenceDates().constFirst().isValid()) {
0332                 return curView->selectedIncidenceDates().constFirst();
0333             }
0334         }
0335     }
0336 
0337     // When all else fails, use the navigator start date, or today.
0338     if (fallbackToToday) {
0339         return QDate::currentDate();
0340     } else {
0341         return mDateNavigator->selectedDates().constFirst();
0342     }
0343 }
0344 
0345 QDate CalendarView::activeIncidenceDate()
0346 {
0347     KOrg::BaseView *curView = mViewManager->currentView();
0348     if (curView) {
0349         KCalendarCore::DateList dates = curView->selectedIncidenceDates();
0350         if (!dates.isEmpty()) {
0351             return dates.first();
0352         }
0353     }
0354 
0355     return {};
0356 }
0357 
0358 QDate CalendarView::startDate()
0359 {
0360     KCalendarCore::DateList dates = mDateNavigator->selectedDates();
0361     return dates.first();
0362 }
0363 
0364 QDate CalendarView::endDate()
0365 {
0366     KCalendarCore::DateList dates = mDateNavigator->selectedDates();
0367     return dates.last();
0368 }
0369 
0370 void CalendarView::createPrinter()
0371 {
0372     if (!mCalPrinter) {
0373         mCalPrinter = new CalendarSupport::CalPrinter(this, mCalendar);
0374         connect(this, &CalendarView::configChanged, mCalPrinter, &CalendarSupport::CalPrinter::updateConfig);
0375     }
0376 }
0377 
0378 bool CalendarView::saveCalendar(const QString &filename)
0379 {
0380     // Store back all unsaved data into calendar object
0381     mViewManager->currentView()->flushView();
0382 
0383     KCalendarCore::FileStorage storage(mCalendar);
0384     storage.setFileName(filename);
0385     storage.setSaveFormat(new KCalendarCore::ICalFormat);
0386 
0387     return storage.save();
0388 }
0389 
0390 void CalendarView::archiveCalendar()
0391 {
0392     mDialogManager->showArchiveDialog();
0393 }
0394 
0395 void CalendarView::readSettings()
0396 {
0397     // read settings from the KConfig, supplying reasonable
0398     // defaults where none are to be found
0399 
0400     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0401     KConfigGroup geometryConfig(config, QStringLiteral("KOrganizer Geometry"));
0402 
0403     QList<int> sizes = geometryConfig.readEntry("Separator1", QList<int>());
0404     if (sizes.count() != 2 || sizes.count() == sizes.count(0)) {
0405         sizes << mDateNavigatorContainer->minimumSizeHint().width();
0406         sizes << 300;
0407     }
0408     mPanner->setSizes(sizes);
0409 
0410     sizes = geometryConfig.readEntry("Separator2", QList<int>());
0411     if (!sizes.isEmpty() && sizes.count() != sizes.count(0)) {
0412         mLeftSplitter->setSizes(sizes);
0413     }
0414 
0415     mViewManager->readSettings(config.data());
0416     mTodoList->restoreLayout(config.data(), QStringLiteral("Sidebar Todo View"), true);
0417 
0418     readFilterSettings(config.data());
0419 
0420     KConfigGroup viewConfig(config, QStringLiteral("Views"));
0421     int dateCount = viewConfig.readEntry("ShownDatesCount", 7);
0422     if (dateCount == 7) {
0423         mDateNavigator->selectWeek();
0424     } else {
0425         const KCalendarCore::DateList dates = mDateNavigator->selectedDates();
0426         if (!dates.isEmpty()) {
0427             mDateNavigator->selectDates(dates.first(), dateCount);
0428         }
0429     }
0430 }
0431 
0432 void CalendarView::writeSettings()
0433 {
0434     auto config = KSharedConfig::openConfig();
0435     KConfigGroup geometryConfig(config, QStringLiteral("KOrganizer Geometry"));
0436 
0437     QList<int> list = mMainSplitterSizes.isEmpty() ? mPanner->sizes() : mMainSplitterSizes;
0438     // splitter sizes are invalid (all zero) unless we have been shown once
0439     if (list.count() != list.count(0) && mSplitterSizesValid) {
0440         geometryConfig.writeEntry("Separator1", list);
0441     }
0442 
0443     list = mLeftSplitter->sizes();
0444     if (list.count() != list.count(0) && mSplitterSizesValid) {
0445         geometryConfig.writeEntry("Separator2", list);
0446     }
0447 
0448     mViewManager->writeSettings(config.data());
0449     mTodoList->saveLayout(config.data(), QStringLiteral("Sidebar Todo View"));
0450 
0451     Akonadi::CalendarSettings::self()->save();
0452     KOPrefs::instance()->save();
0453     CalendarSupport::KCalPrefs::instance()->save();
0454 
0455     writeFilterSettings(config.data());
0456 
0457     KConfigGroup viewConfig(config, QStringLiteral("Views"));
0458     viewConfig.writeEntry("ShownDatesCount", mDateNavigator->selectedDates().count());
0459 
0460     config->sync();
0461 }
0462 
0463 void CalendarView::readFilterSettings(KConfig *config)
0464 {
0465     qDeleteAll(mFilters);
0466     mFilters.clear();
0467 
0468     KConfigGroup generalConfig(config, QStringLiteral("General"));
0469     // FIXME: Move the filter loading and saving to the CalFilter class in libkcal
0470     QStringList filterList = generalConfig.readEntry("CalendarFilters", QStringList());
0471     QString currentFilter = generalConfig.readEntry("Current Filter");
0472 
0473     QStringList::ConstIterator it = filterList.constBegin();
0474     QStringList::ConstIterator end = filterList.constEnd();
0475     while (it != end) {
0476         auto filter = new KCalendarCore::CalFilter(*it);
0477         KConfigGroup filterConfig(config, QStringLiteral("Filter_") + (*it));
0478         filter->setCriteria(filterConfig.readEntry("Criteria", 0));
0479         filter->setCategoryList(filterConfig.readEntry("CategoryList", QStringList()));
0480         if (filter->criteria() & KCalendarCore::CalFilter::HideNoMatchingAttendeeTodos) {
0481             filter->setEmailList(CalendarSupport::KCalPrefs::instance()->allEmails());
0482         }
0483         filter->setCompletedTimeSpan(filterConfig.readEntry("HideTodoDays", 0));
0484         mFilters.append(filter);
0485 
0486         ++it;
0487     }
0488 
0489     int pos = filterList.indexOf(currentFilter);
0490     mCurrentFilter = nullptr;
0491     if (pos >= 0) {
0492         mCurrentFilter = mFilters.at(pos);
0493     }
0494     updateFilter();
0495 }
0496 
0497 void CalendarView::writeFilterSettings(KConfig *config)
0498 {
0499     QStringList filterList;
0500 
0501     const QStringList oldFilterList = config->groupList().filter(QRegularExpression(QStringLiteral("^Filter_.*")));
0502     // Delete Old Group
0503     for (const QString &conf : oldFilterList) {
0504         KConfigGroup group = config->group(conf);
0505         group.deleteGroup(QLatin1StringView());
0506     }
0507 
0508     filterList.reserve(mFilters.count());
0509     for (KCalendarCore::CalFilter *filter : std::as_const(mFilters)) {
0510         filterList << filter->name();
0511         KConfigGroup filterConfig(config, QStringLiteral("Filter_") + filter->name());
0512         filterConfig.writeEntry("Criteria", filter->criteria());
0513         filterConfig.writeEntry("CategoryList", filter->categoryList());
0514         filterConfig.writeEntry("HideTodoDays", filter->completedTimeSpan());
0515     }
0516     KConfigGroup generalConfig(config, QStringLiteral("General"));
0517     generalConfig.writeEntry("CalendarFilters", filterList);
0518     if (mCurrentFilter) {
0519         generalConfig.writeEntry("Current Filter", mCurrentFilter->name());
0520     } else {
0521         generalConfig.writeEntry("Current Filter", QString());
0522     }
0523 }
0524 
0525 void CalendarView::goDate(QDate date)
0526 {
0527     mDateNavigator->selectDate(date);
0528 }
0529 
0530 void CalendarView::showDate(QDate date)
0531 {
0532     int dateCount = mDateNavigator->datesCount();
0533     if (dateCount == 7) {
0534         mDateNavigator->selectWeek(date);
0535     } else {
0536         mDateNavigator->selectDates(date, dateCount);
0537     }
0538 }
0539 
0540 void CalendarView::goToday()
0541 {
0542     mDateNavigator->selectToday();
0543 }
0544 
0545 void CalendarView::goNext()
0546 {
0547     if (qobject_cast<MonthView *>(mViewManager->currentView())) {
0548         const QDate month = mDateNavigatorContainer->monthOfNavigator(0);
0549         QPair<QDate, QDate> limits = KODayMatrix::matrixLimits(month);
0550         mDateNavigator->selectNextMonth(month, limits.first, limits.second);
0551     } else {
0552         mDateNavigator->selectNext();
0553     }
0554 }
0555 
0556 void CalendarView::goPrevious()
0557 {
0558     if (qobject_cast<MonthView *>(mViewManager->currentView())) {
0559         const QDate month = mDateNavigatorContainer->monthOfNavigator(0);
0560         QPair<QDate, QDate> limits = KODayMatrix::matrixLimits(month);
0561         mDateNavigator->selectPreviousMonth(month, limits.first, limits.second);
0562     } else {
0563         mDateNavigator->selectPrevious();
0564     }
0565 }
0566 
0567 void CalendarView::updateConfig()
0568 {
0569     if (mCalPrinter) {
0570         mCalPrinter->deleteLater();
0571         mCalPrinter = nullptr;
0572     }
0573 
0574     KOGlobals::self()->setHolidays(KOPrefs::instance()->mHolidays);
0575 
0576     // config changed lets tell the date navigator the new modes
0577     // if there weren't changed they are ignored
0578     updateHighlightModes();
0579 
0580     Q_EMIT configChanged();
0581 
0582     // switch between merged, side by side and tabbed agenda if needed
0583     mViewManager->updateMultiCalendarDisplay();
0584 
0585     // To make the "fill window" configurations work
0586     mViewManager->raiseCurrentView();
0587 
0588     mChanger->setDestinationPolicy(static_cast<Akonadi::IncidenceChanger::DestinationPolicy>(KOPrefs::instance()->destination()));
0589 
0590     mChanger->setGroupwareCommunication(CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication());
0591 }
0592 
0593 void CalendarView::slotCreateFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString)
0594 {
0595     Q_UNUSED(changeId)
0596     if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) {
0597         changeIncidenceDisplay(item, Akonadi::IncidenceChanger::ChangeTypeCreate);
0598         updateUnmanagedViews();
0599         checkForFilteredChange(item);
0600     } else if (!errorString.isEmpty()) {
0601         qCCritical(KORGANIZER_LOG) << "Incidence not added, job reported error: " << errorString;
0602     }
0603 }
0604 
0605 void CalendarView::slotModifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString)
0606 {
0607     Q_UNUSED(changeId)
0608     if (resultCode != Akonadi::IncidenceChanger::ResultCodeSuccess) {
0609         qCCritical(KORGANIZER_LOG) << "Incidence not modified, job reported error: " << errorString;
0610         return;
0611     }
0612 
0613     Q_ASSERT(item.isValid());
0614     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
0615     Q_ASSERT(incidence);
0616     QSet<KCalendarCore::IncidenceBase::Field> dirtyFields = incidence->dirtyFields();
0617     incidence->resetDirtyFields();
0618     // Record completed todos in journals, if enabled. we should to this here in
0619     // favor of the todolist. users can mark a task as completed in an editor
0620     // as well.
0621     if (incidence->type() == KCalendarCore::Incidence::TypeTodo && KOPrefs::instance()->recordTodosInJournals()
0622         && (dirtyFields.contains(KCalendarCore::Incidence::FieldCompleted))) {
0623         KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>();
0624         if (todo->isCompleted() || todo->recurs()) {
0625             QString timeStr = QLocale::system().toString(QTime::currentTime(), QLocale::ShortFormat);
0626             QString description = i18n("Todo completed: %1 (%2)", incidence->summary(), timeStr);
0627 
0628             KCalendarCore::Journal::List journals = calendar()->journals(QDate::currentDate());
0629 
0630             if (journals.isEmpty()) {
0631                 KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
0632                 journal->setDtStart(QDateTime::currentDateTime());
0633 
0634                 QString dateStr = QLocale::system().toString(QDate::currentDate(), QLocale::LongFormat);
0635                 journal->setSummary(i18n("Journal of %1", dateStr));
0636                 journal->setDescription(description);
0637 
0638                 if (mChanger->createIncidence(journal, item.parentCollection(), this) == -1) {
0639                     qCCritical(KORGANIZER_LOG) << "Unable to add Journal";
0640                     return;
0641                 }
0642             } else { // journal list is not empty
0643                 Akonadi::Item journalItem = mCalendar->item(journals.first()->uid());
0644                 KCalendarCore::Journal::Ptr journal = Akonadi::CalendarUtils::journal(journalItem);
0645                 KCalendarCore::Journal::Ptr oldJournal(journal->clone());
0646                 journal->setDescription(journal->description().append(QLatin1Char('\n') + description));
0647                 (void)mChanger->modifyIncidence(journalItem, oldJournal, this);
0648             }
0649         }
0650     }
0651 
0652     changeIncidenceDisplay(item, Akonadi::IncidenceChanger::ChangeTypeCreate);
0653     updateUnmanagedViews();
0654     checkForFilteredChange(item);
0655 }
0656 
0657 void CalendarView::slotDeleteFinished(int changeId,
0658                                       const QList<Akonadi::Item::Id> &itemIdList,
0659                                       Akonadi::IncidenceChanger::ResultCode resultCode,
0660                                       const QString &errorString)
0661 {
0662     Q_UNUSED(changeId)
0663     if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) {
0664         for (Akonadi::Item::Id id : itemIdList) {
0665             Akonadi::Item item = mCalendar->item(id);
0666             if (item.isValid()) {
0667                 changeIncidenceDisplay(item, Akonadi::IncidenceChanger::ChangeTypeDelete);
0668             }
0669         }
0670         updateUnmanagedViews();
0671     } else {
0672         qCCritical(KORGANIZER_LOG) << "Incidence not deleted, job reported error: " << errorString;
0673     }
0674 }
0675 
0676 void CalendarView::checkForFilteredChange(const Akonadi::Item &item)
0677 {
0678     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
0679     KCalendarCore::CalFilter *filter = calendar()->filter();
0680     if (filter && !filter->filterIncidence(incidence)) {
0681         // Incidence is filtered and thus not shown in the view, tell the
0682         // user so that he isn't surprised if his new event doesn't show up
0683         mMessageWidget->setText(
0684             i18n("The item \"%1\" is filtered by your current filter rules, "
0685                  "so it will be hidden and not appear in the view.",
0686                  incidence->summary()));
0687         mMessageWidget->show();
0688     }
0689 }
0690 
0691 void CalendarView::startMultiModify(const QString &text)
0692 {
0693     mChanger->startAtomicOperation(text);
0694 }
0695 
0696 void CalendarView::endMultiModify()
0697 {
0698     mChanger->endAtomicOperation();
0699 }
0700 
0701 void CalendarView::changeIncidenceDisplay(const Akonadi::Item &item, Akonadi::IncidenceChanger::ChangeType changeType)
0702 {
0703     if (mDateNavigatorContainer->isVisible()) {
0704         mDateNavigatorContainer->updateView();
0705     }
0706 
0707     if (CalendarSupport::hasIncidence(item)) {
0708         // If there is an event view visible update the display
0709         mViewManager->currentView()->changeIncidenceDisplay(item, changeType);
0710     } else {
0711         mViewManager->currentView()->updateView();
0712     }
0713 }
0714 
0715 void CalendarView::updateView(const QDate &start, const QDate &end, const QDate &preferredMonth, const bool updateTodos)
0716 {
0717     const bool currentViewIsTodoView = mViewManager->currentView()->identifier() == "DefaultTodoView";
0718 
0719     if (updateTodos && !currentViewIsTodoView && mTodoList->isVisible()) {
0720         // Update the sidepane todoView
0721         mTodoList->updateView();
0722     }
0723 
0724     if (start.isValid() && end.isValid() && !(currentViewIsTodoView && !updateTodos)) {
0725         mViewManager->updateView(start, end, preferredMonth);
0726     }
0727 
0728     if (mDateNavigatorContainer->isVisible()) {
0729         mDateNavigatorContainer->updateView();
0730     }
0731 }
0732 
0733 void CalendarView::updateView()
0734 {
0735     const KCalendarCore::DateList tmpList = mDateNavigator->selectedDates();
0736     const QDate month = mDateNavigatorContainer->monthOfNavigator();
0737 
0738     // We assume that the navigator only selects consecutive days.
0739     updateView(tmpList.first(), tmpList.last(), month /**preferredMonth*/);
0740 }
0741 
0742 void CalendarView::updateUnmanagedViews()
0743 {
0744     if (mDateNavigatorContainer->isVisible()) {
0745         mDateNavigatorContainer->updateDayMatrix();
0746     }
0747 }
0748 
0749 int CalendarView::msgItemDelete(const Akonadi::Item &item)
0750 {
0751     return KMessageBox::warningContinueCancel(
0752         this,
0753         i18nc("@info", "Do you really want to permanently remove the item \"%1\"?", Akonadi::CalendarUtils::incidence(item)->summary()),
0754         i18nc("@title:window", "Delete Item?"),
0755         KStandardGuiItem::del());
0756 }
0757 
0758 void CalendarView::edit_cut()
0759 {
0760     const Akonadi::Item item = selectedIncidence();
0761     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
0762     if (!incidence) {
0763         qCCritical(KORGANIZER_LOG) << "Null incidence";
0764         return;
0765     }
0766 
0767     mCalendarClipboard->cutIncidence(incidence, Akonadi::CalendarClipboard::AskMode);
0768 }
0769 
0770 void CalendarView::edit_copy()
0771 {
0772     const Akonadi::Item item = selectedIncidence();
0773 
0774     if (!item.isValid()) {
0775         qCCritical(KORGANIZER_LOG) << "Invalid item";
0776         return;
0777     }
0778 
0779     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
0780     Q_ASSERT(incidence);
0781     if (!mCalendarClipboard->copyIncidence(incidence, Akonadi::CalendarClipboard::AskMode)) {
0782         qCCritical(KORGANIZER_LOG) << "Error copying incidence";
0783     }
0784 
0785     checkClipboard();
0786 }
0787 
0788 void CalendarView::edit_paste()
0789 {
0790     // If in agenda and month view, use the selected time and date from there.
0791     // In all other cases, use the navigator's selected date.
0792 
0793     QDateTime endDT;
0794     QDateTime finalDateTime;
0795     bool useEndTime = false;
0796     KCalUtils::DndFactory::PasteFlags pasteFlags = {};
0797 
0798     KOrg::BaseView *curView = mViewManager->currentView();
0799     KOAgendaView *agendaView = mViewManager->agendaView();
0800     MonthView *monthView = mViewManager->monthView();
0801 
0802     if (!curView) {
0803         qCWarning(KORGANIZER_LOG) << "No view is selected, can't paste";
0804         return;
0805     }
0806 
0807     if (curView == agendaView && agendaView->selectionStart().isValid()) {
0808         const QDate date = agendaView->selectionStart().date();
0809         endDT = agendaView->selectionEnd();
0810         useEndTime = !agendaView->selectedIsSingleCell();
0811         if (agendaView->selectedIsAllDay()) {
0812             finalDateTime = QDateTime(date.startOfDay());
0813         } else {
0814             finalDateTime = QDateTime(date, agendaView->selectionStart().time());
0815         }
0816     } else if (curView == monthView && monthView->selectionStart().isValid()) {
0817         finalDateTime = QDateTime(monthView->selectionStart().date().startOfDay());
0818         pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime;
0819     } else if (!mDateNavigator->selectedDates().isEmpty() && curView->supportsDateNavigation()) {
0820         // default to the selected date from the navigator
0821         const KCalendarCore::DateList dates = mDateNavigator->selectedDates();
0822         if (!dates.isEmpty()) {
0823             finalDateTime = QDateTime(dates.first().startOfDay());
0824             pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime;
0825         }
0826     }
0827 
0828     if (!finalDateTime.isValid() && curView->supportsDateNavigation()) {
0829         KMessageBox::error(this, i18n("Paste failed: unable to determine a valid target date."));
0830         return;
0831     }
0832 
0833     KCalUtils::DndFactory factory(mCalendar);
0834 
0835     KCalendarCore::Incidence::List pastedIncidences = factory.pasteIncidences(finalDateTime, pasteFlags);
0836     KCalendarCore::Incidence::List::Iterator it;
0837 
0838     for (it = pastedIncidences.begin(); it != pastedIncidences.end(); ++it) {
0839         // FIXME: use a visitor here
0840         if ((*it)->type() == KCalendarCore::Incidence::TypeEvent) {
0841             KCalendarCore::Event::Ptr pastedEvent = (*it).staticCast<KCalendarCore::Event>();
0842             // only use selected area if event is of the same type (all-day or non-all-day
0843             // as the current selection is
0844             if (agendaView && endDT.isValid() && useEndTime) {
0845                 if ((pastedEvent->allDay() && agendaView->selectedIsAllDay()) || (!pastedEvent->allDay() && !agendaView->selectedIsAllDay())) {
0846                     QDateTime kdt = endDT.toLocalTime();
0847                     pastedEvent->setDtEnd(kdt.toTimeZone(pastedEvent->dtEnd().timeZone()));
0848                 }
0849             }
0850 
0851             pastedEvent->setRelatedTo(QString());
0852             (void)mChanger->createIncidence(KCalendarCore::Event::Ptr(pastedEvent->clone()), Akonadi::Collection(), this);
0853         } else if ((*it)->type() == KCalendarCore::Incidence::TypeTodo) {
0854             KCalendarCore::Todo::Ptr pastedTodo = (*it).staticCast<KCalendarCore::Todo>();
0855             Akonadi::Item _selectedTodoItem = selectedTodo();
0856 
0857             // if we are cutting a hierarchy only the root
0858             // should be son of _selectedTodo
0859             KCalendarCore::Todo::Ptr _selectedTodo = Akonadi::CalendarUtils::todo(_selectedTodoItem);
0860             if (_selectedTodo && pastedTodo->relatedTo().isEmpty()) {
0861                 pastedTodo->setRelatedTo(_selectedTodo->uid());
0862             }
0863 
0864             // When pasting multiple incidences, don't ask which collection to use, for each one
0865             (void)mChanger->createIncidence(KCalendarCore::Todo::Ptr(pastedTodo->clone()), Akonadi::Collection(), this);
0866         } else if ((*it)->type() == KCalendarCore::Incidence::TypeJournal) {
0867             // When pasting multiple incidences, don't ask which collection to use, for each one
0868             (void)mChanger->createIncidence(KCalendarCore::Incidence::Ptr((*it)->clone()), Akonadi::Collection(), this);
0869         }
0870     }
0871 }
0872 
0873 void CalendarView::edit_options()
0874 {
0875     mDialogManager->showOptionsDialog();
0876 }
0877 
0878 static QTime nextQuarterHour(const QTime &time)
0879 {
0880     if (time.second() % 900) {
0881         return time.addSecs(900 - (time.minute() * 60 + time.second()) % 900);
0882     }
0883     return time; // roundup not needed
0884 }
0885 
0886 void CalendarView::dateTimesForNewEvent(QDateTime &startDt, QDateTime &endDt, bool &allDay)
0887 {
0888     mViewManager->currentView()->eventDurationHint(startDt, endDt, allDay);
0889 
0890     if (!startDt.isValid() || !endDt.isValid()) {
0891         startDt.setDate(activeDate(true));
0892 
0893         QTime prefTime;
0894         if (CalendarSupport::KCalPrefs::instance()->startTime().isValid()) {
0895             prefTime = CalendarSupport::KCalPrefs::instance()->startTime().time();
0896         }
0897 
0898         const QDateTime currentDateTime = QDateTime::currentDateTime();
0899         if (startDt.date() == currentDateTime.date()) {
0900             // If today and the current time is already past the default time
0901             // use the next quarter hour after the current time.
0902             // but don't spill over into tomorrow.
0903             const QTime currentTime = currentDateTime.time();
0904             if (!prefTime.isValid() || (currentTime > prefTime && currentTime < QTime(23, 45))) {
0905                 prefTime = nextQuarterHour(currentTime);
0906             }
0907         }
0908         startDt.setTime(prefTime);
0909 
0910         int addSecs = (CalendarSupport::KCalPrefs::instance()->mDefaultDuration.time().hour() * 3600)
0911             + (CalendarSupport::KCalPrefs::instance()->mDefaultDuration.time().minute() * 60);
0912         endDt = startDt.addSecs(addSecs);
0913     }
0914 }
0915 
0916 IncidenceEditorNG::IncidenceDialog *CalendarView::incidenceDialog(const Akonadi::Item &item)
0917 {
0918     IncidenceEditorNG::IncidenceDialog *dialog = mDialogManager->createDialog(item);
0919     connect(dialog, &IncidenceEditorNG::IncidenceDialog::incidenceCreated, this, &CalendarView::handleIncidenceCreated);
0920     return dialog;
0921 }
0922 
0923 IncidenceEditorNG::IncidenceDialog *CalendarView::newEventEditor(const KCalendarCore::Event::Ptr &event)
0924 {
0925     Akonadi::Item item;
0926     item.setPayload(event);
0927     IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog(item);
0928 
0929     dialog->load(item);
0930 
0931     mDialogManager->connectTypeAhead(dialog, qobject_cast<KOEventView *>(viewManager()->currentView()));
0932 
0933     return dialog;
0934 }
0935 
0936 void CalendarView::newEvent()
0937 {
0938     newEvent(QDateTime(), QDateTime());
0939 }
0940 
0941 void CalendarView::newEvent(const QDate &dt)
0942 {
0943     QDateTime startDt(dt, CalendarSupport::KCalPrefs::instance()->mStartTime.time());
0944     QTime duration = CalendarSupport::KCalPrefs::instance()->defaultDuration().time();
0945     QTime time = startDt.time();
0946 
0947     time = time.addSecs(duration.hour() * 3600 + duration.minute() * 60 + duration.second());
0948     QDateTime endDt(startDt);
0949     endDt.setTime(time);
0950     newEvent(startDt, endDt);
0951 }
0952 
0953 void CalendarView::newEvent(const QDateTime &startDt)
0954 {
0955     newEvent(startDt, startDt);
0956 }
0957 
0958 void CalendarView::newEvent(const QDateTime &startDtParam, const QDateTime &endDtParam, bool allDay)
0959 {
0960     // Let the current view change the default start/end datetime
0961     QDateTime startDt(startDtParam);
0962     QDateTime endDt(endDtParam);
0963 
0964     // Adjust the start/end date times (i.e. replace invalid values by defaults,
0965     // and let the view adjust the type.
0966     dateTimesForNewEvent(startDt, endDt, allDay);
0967 
0968     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
0969     defaults.setStartDateTime(startDt);
0970     defaults.setEndDateTime(endDt);
0971 
0972     KCalendarCore::Event::Ptr event(new KCalendarCore::Event);
0973     defaults.setDefaults(event);
0974     event->setAllDay(allDay);
0975 
0976     IncidenceEditorNG::IncidenceDialog *eventEditor = newEventEditor(event);
0977     Q_ASSERT(eventEditor);
0978 
0979     // Fallsback to the default collection defined in config
0980     eventEditor->selectCollection(defaultCollection(KCalendarCore::Event::eventMimeType()));
0981 }
0982 
0983 void CalendarView::newEvent(const QString &summary,
0984                             const QString &description,
0985                             const QStringList &attachments,
0986                             const QStringList &attendees,
0987                             const QStringList &attachmentMimetypes,
0988                             bool inlineAttachment)
0989 {
0990     Akonadi::Collection defaultCol = defaultCollection(KCalendarCore::Event::eventMimeType());
0991 
0992     IncidenceEditorNG::IncidenceDialogFactory::createEventEditor(summary,
0993                                                                  description,
0994                                                                  attachments,
0995                                                                  attendees,
0996                                                                  attachmentMimetypes,
0997                                                                  QStringList() /* attachment labels */,
0998                                                                  inlineAttachment,
0999                                                                  defaultCol,
1000                                                                  true /* cleanupAttachmentTempFiles */,
1001                                                                  this /* parent */);
1002 }
1003 
1004 void CalendarView::newTodo(const QString &summary,
1005                            const QString &description,
1006                            const QStringList &attachments,
1007                            const QStringList &attendees,
1008                            const QStringList &attachmentMimetypes,
1009                            bool inlineAttachment)
1010 {
1011     Akonadi::Collection defaultCol = defaultCollection(KCalendarCore::Todo::todoMimeType());
1012 
1013     IncidenceEditorNG::IncidenceDialogFactory::createTodoEditor(summary,
1014                                                                 description,
1015                                                                 attachments,
1016                                                                 attendees,
1017                                                                 attachmentMimetypes,
1018                                                                 QStringList() /* attachment labels */,
1019                                                                 inlineAttachment,
1020                                                                 defaultCol,
1021                                                                 true /* cleanupAttachmentTempFiles */,
1022                                                                 this /* parent */);
1023 }
1024 
1025 void CalendarView::newTodo()
1026 {
1027     newTodo(Akonadi::Collection());
1028 }
1029 
1030 void CalendarView::newTodo(const Akonadi::Collection &collection)
1031 {
1032     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1033 
1034     bool allDay = true;
1035     if (mViewManager->currentView()->isEventView()) {
1036         QDateTime startDt;
1037         QDateTime endDt;
1038         dateTimesForNewEvent(startDt, endDt, allDay);
1039 
1040         defaults.setStartDateTime(startDt);
1041         defaults.setEndDateTime(endDt);
1042     }
1043 
1044     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1045     defaults.setDefaults(todo);
1046     todo->setAllDay(allDay);
1047 
1048     Akonadi::Item item;
1049     item.setPayload(todo);
1050 
1051     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1052 
1053     dialog->load(item);
1054 }
1055 
1056 void CalendarView::newTodo(const QDate &date)
1057 {
1058     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1059     defaults.setEndDateTime(QDateTime(date, QTime::currentTime()));
1060 
1061     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1062     defaults.setDefaults(todo);
1063     todo->setAllDay(true);
1064 
1065     Akonadi::Item item;
1066     item.setPayload(todo);
1067 
1068     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item);
1069     dialog->load(item);
1070 }
1071 
1072 void CalendarView::newJournal()
1073 {
1074     newJournal(QString(), activeDate(true));
1075 }
1076 
1077 void CalendarView::newJournal(const QDate &date)
1078 {
1079     newJournal(QString(), date.isValid() ? date : activeDate(true));
1080 }
1081 
1082 void CalendarView::newJournal(const Akonadi::Collection &collection)
1083 {
1084     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1085 
1086     if (mViewManager->currentView()->isEventView()) {
1087         QDateTime startDt;
1088         QDateTime endDt;
1089         bool allDay = true;
1090         dateTimesForNewEvent(startDt, endDt, allDay);
1091 
1092         defaults.setStartDateTime(startDt);
1093         defaults.setEndDateTime(endDt);
1094     }
1095 
1096     KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
1097     defaults.setDefaults(journal);
1098 
1099     Akonadi::Item item;
1100     item.setPayload(journal);
1101     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1102     dialog->load(item);
1103 }
1104 
1105 void CalendarView::newJournal(const QString &text, QDate date)
1106 {
1107     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1108 
1109     KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
1110     defaults.setStartDateTime(QDateTime(date.startOfDay()));
1111     defaults.setDefaults(journal);
1112 
1113     journal->setSummary(text);
1114 
1115     Akonadi::Item item;
1116     item.setPayload(journal);
1117 
1118     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item);
1119     dialog->load(item);
1120 }
1121 
1122 KOrg::BaseView *CalendarView::currentView() const
1123 {
1124     return mViewManager->currentView();
1125 }
1126 
1127 void CalendarView::configureCurrentView()
1128 {
1129     KOrg::BaseView *const view = currentView();
1130     if (view && view->hasConfigurationDialog()) {
1131         view->showConfigurationDialog(this);
1132     }
1133 }
1134 
1135 void CalendarView::newSubTodo()
1136 {
1137     const Akonadi::Item item = selectedTodo();
1138     if (CalendarSupport::hasTodo(item)) {
1139         newSubTodo(item);
1140     }
1141 }
1142 
1143 void CalendarView::newSubTodo(const Akonadi::Collection &collection)
1144 {
1145     if (!CalendarSupport::hasTodo(selectedTodo())) {
1146         qCWarning(KORGANIZER_LOG) << "CalendarSupport::hasTodo() is false";
1147         return;
1148     }
1149 
1150     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1151     defaults.setRelatedIncidence(Akonadi::CalendarUtils::incidence(selectedTodo()));
1152 
1153     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1154     defaults.setDefaults(todo);
1155 
1156     Akonadi::Item item;
1157     item.setPayload(todo);
1158 
1159     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1160     dialog->load(item);
1161 }
1162 
1163 void CalendarView::newSubTodo(const Akonadi::Item &parentTodo)
1164 {
1165     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1166     defaults.setRelatedIncidence(Akonadi::CalendarUtils::incidence(parentTodo));
1167 
1168     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1169     defaults.setDefaults(todo);
1170 
1171     Q_ASSERT(!todo->relatedTo().isEmpty());
1172 
1173     Akonadi::Item item;
1174     item.setPayload(todo);
1175 
1176     // Don't use parentTodo.parentCollection() because that can be a search folder.
1177     Akonadi::Collection collection = mCalendar->collection(parentTodo.storageCollectionId());
1178     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1179     dialog->load(item);
1180 }
1181 
1182 void CalendarView::newFloatingEvent()
1183 {
1184     const QDate date = activeDate();
1185     newEvent(QDateTime(date, QTime(12, 0, 0)), QDateTime(date, QTime(12, 0, 0)), true);
1186 }
1187 
1188 bool CalendarView::addIncidence(const QString &ical)
1189 {
1190     KCalendarCore::ICalFormat format;
1191     format.setTimeZone(mCalendar->timeZone());
1192     KCalendarCore::Incidence::Ptr incidence(format.fromString(ical));
1193     return addIncidence(incidence);
1194 }
1195 
1196 bool CalendarView::addIncidence(const KCalendarCore::Incidence::Ptr &incidence)
1197 {
1198     return incidence ? mChanger->createIncidence(incidence, Akonadi::Collection(), this) != -1 : false;
1199 }
1200 
1201 void CalendarView::appointment_show()
1202 {
1203     const Akonadi::Item item = selectedIncidence();
1204     if (CalendarSupport::hasIncidence(item)) {
1205         showIncidence(item);
1206     }
1207 }
1208 
1209 void CalendarView::appointment_edit()
1210 {
1211     const Akonadi::Item item = selectedIncidence();
1212     if (CalendarSupport::hasIncidence(item)) {
1213         editIncidence(item);
1214     }
1215 }
1216 
1217 void CalendarView::appointment_delete()
1218 {
1219     const Akonadi::Item item = selectedIncidence();
1220     if (CalendarSupport::hasIncidence(item)) {
1221         deleteIncidence(item);
1222     }
1223 }
1224 
1225 void CalendarView::todo_unsub()
1226 {
1227     const Akonadi::Item aTodo = selectedTodo();
1228     if (incidence_unsub(aTodo)) {
1229         updateView();
1230     }
1231 }
1232 
1233 bool CalendarView::incidence_unsub(const Akonadi::Item &item)
1234 {
1235     const KCalendarCore::Incidence::Ptr inc = Akonadi::CalendarUtils::incidence(item);
1236 
1237     if (!inc || inc->relatedTo().isEmpty()) {
1238         qCDebug(KORGANIZER_LOG) << "Refusing to unparent this instance" << inc;
1239         return false;
1240     }
1241 
1242     for (const auto &instance : mCalendar->instances(inc)) {
1243         KCalendarCore::Incidence::Ptr oldInstance(instance->clone());
1244         instance->setRelatedTo(QString());
1245         (void)mChanger->modifyIncidence(mCalendar->item(instance), oldInstance, this);
1246     }
1247 
1248     KCalendarCore::Incidence::Ptr oldInc(inc->clone());
1249     inc->setRelatedTo(QString());
1250     (void)mChanger->modifyIncidence(item, oldInc, this);
1251 
1252     return true;
1253 }
1254 
1255 bool CalendarView::makeSubTodosIndependent()
1256 {
1257     const Akonadi::Item aTodo = selectedTodo();
1258 
1259     startMultiModify(i18n("Make sub-to-dos independent"));
1260     bool status = makeChildrenIndependent(aTodo);
1261     endMultiModify();
1262     if (status) {
1263         updateView();
1264     }
1265     return status;
1266 }
1267 
1268 bool CalendarView::makeChildrenIndependent(const Akonadi::Item &item)
1269 {
1270     const KCalendarCore::Incidence::Ptr inc = Akonadi::CalendarUtils::incidence(item);
1271 
1272     const Akonadi::Item::List subIncs = mCalendar->childItems(item.id());
1273 
1274     if (!inc || subIncs.isEmpty()) {
1275         qCDebug(KORGANIZER_LOG) << "Refusing to  make children independent" << inc;
1276         return false;
1277     }
1278 
1279     for (const Akonadi::Item &subInc : subIncs) {
1280         incidence_unsub(subInc);
1281     }
1282 
1283     return true;
1284 }
1285 
1286 bool CalendarView::deleteIncidence(Akonadi::Item::Id id, bool force)
1287 {
1288     Akonadi::Item item = mCalendar->item(id);
1289     if (!CalendarSupport::hasIncidence(item)) {
1290         qCCritical(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): Item does not contain incidence.";
1291         return false;
1292     }
1293     return deleteIncidence(item, force);
1294 }
1295 
1296 void CalendarView::toggleAlarm(const Akonadi::Item &item)
1297 {
1298     const KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
1299     if (!incidence) {
1300         qCCritical(KORGANIZER_LOG) << "Null incidence";
1301         return;
1302     }
1303     KCalendarCore::Incidence::Ptr oldincidence(incidence->clone());
1304 
1305     const KCalendarCore::Alarm::List alarms = incidence->alarms();
1306     KCalendarCore::Alarm::List::ConstIterator it;
1307     KCalendarCore::Alarm::List::ConstIterator end(alarms.constEnd());
1308     for (it = alarms.constBegin(); it != end; ++it) {
1309         (*it)->toggleAlarm();
1310     }
1311     if (alarms.isEmpty()) {
1312         // Add an alarm if it didn't have one
1313         KCalendarCore::Alarm::Ptr alm = incidence->newAlarm();
1314         CalendarSupport::createAlarmReminder(alm, incidence->type());
1315     }
1316     mChanger->startAtomicOperation(i18n("Toggle Reminder"));
1317     (void)mChanger->modifyIncidence(item, oldincidence, this);
1318     mChanger->endAtomicOperation();
1319 }
1320 
1321 void CalendarView::toggleTodoCompleted(const Akonadi::Item &todoItem)
1322 {
1323     const KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(todoItem);
1324 
1325     if (!incidence) {
1326         qCCritical(KORGANIZER_LOG) << "Null incidence";
1327         return;
1328     }
1329     if (incidence->type() != KCalendarCore::Incidence::TypeTodo) {
1330         qCDebug(KORGANIZER_LOG) << "called for a non-Todo incidence";
1331         return;
1332     }
1333 
1334     KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem);
1335     Q_ASSERT(todo);
1336     KCalendarCore::Todo::Ptr oldtodo(todo->clone());
1337 
1338     if (todo->isCompleted()) {
1339         todo->setPercentComplete(0);
1340         todo->setCompleted(false);
1341         todo->setStatus(KCalendarCore::Incidence::StatusNone);
1342     } else {
1343         todo->setPercentComplete(0);
1344         todo->setCompleted(QDateTime::currentDateTime());
1345         todo->setStatus(KCalendarCore::Incidence::StatusCompleted);
1346     }
1347 
1348     mChanger->startAtomicOperation(i18n("Toggle To-do Completed"));
1349     (void)mChanger->modifyIncidence(todoItem, oldtodo, this);
1350     mChanger->endAtomicOperation();
1351 }
1352 
1353 void CalendarView::toggleCompleted(KCalendarCore::Todo::Ptr todo, const QDate &occurrenceDate)
1354 {
1355     if (todo->recurs()) {
1356         QDateTime recurrenceId = recurrenceOnDate(todo, occurrenceDate);
1357         const auto dtRecurrence = todo->dtRecurrence();
1358         if (todo->isCompleted()) {
1359             todo->setCompleted(false);
1360             todo->setDtRecurrence(recurrenceId);
1361         } else if (dtRecurrence <= recurrenceId) {
1362             // Occurrence is not complete.
1363             const auto dtNextOccurrence = todo->recurrence()->getNextDateTime(recurrenceId);
1364             if (dtNextOccurrence.isValid()) {
1365                 todo->setDtRecurrence(dtNextOccurrence);
1366             } else {
1367                 todo->setDtRecurrence(recurrenceId);
1368                 todo->setCompleted(true);
1369             }
1370         } else {
1371             todo->setDtRecurrence(recurrenceId);
1372         }
1373     } else {
1374         if (todo->isCompleted()) {
1375             todo->setPercentComplete(0);
1376             todo->setCompleted(false);
1377             todo->setStatus(KCalendarCore::Incidence::StatusNone);
1378         } else {
1379             todo->setPercentComplete(0);
1380             todo->setCompleted(QDateTime::currentDateTime());
1381             todo->setStatus(KCalendarCore::Incidence::StatusCompleted);
1382         }
1383     }
1384 }
1385 
1386 void CalendarView::toggleOccurrenceCompleted(const Akonadi::Item &todoItem, const QDate &occurrenceDate)
1387 {
1388     KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem);
1389     if (!todo) {
1390         qWarning(KORGANIZER_LOG) << "item does not contain a todo.";
1391         return;
1392     }
1393     if (todo->recurs() && !occurrenceDate.isValid()) {
1394         qWarning(KORGANIZER_LOG) << "todo recurs, but no occurrence date was provided.";
1395         return;
1396     }
1397 
1398     KCalendarCore::Todo::Ptr oldtodo{todo->clone()};
1399     toggleCompleted(todo, occurrenceDate);
1400     mChanger->startAtomicOperation(i18n("Toggle To-do Completed"));
1401     (void)mChanger->modifyIncidence(todoItem, oldtodo, this);
1402     mChanger->endAtomicOperation();
1403 }
1404 
1405 void CalendarView::copyIncidenceToResource(const Akonadi::Item &item, const Akonadi::Collection &col)
1406 {
1407 #ifdef AKONADI_PORT_DISABLED
1408     if (!incidence) {
1409         qCCritical(KORGANIZER_LOG) << "Null incidence";
1410         return;
1411     }
1412 
1413     KCalendarCore::CalendarResources *const resources = KOrg::StdCalendar::self();
1414     KCalendarCore::CalendarResourceManager *const manager = resources->resourceManager();
1415 
1416     // Find the resource the incidence should be copied to
1417     ResourceCalendar *newCal = nullptr;
1418     KCalendarCore::CalendarResourceManager::iterator it;
1419     for (it = manager->begin(); it != manager->end(); ++it) {
1420         ResourceCalendar *const resource = *it;
1421         if (resource->identifier() == resourceId) {
1422             newCal = resource;
1423             break;
1424         }
1425     }
1426     if (!newCal) {
1427         return;
1428     }
1429 
1430     // Clone a new Incidence from the selected Incidence and give it a new Uid.
1431     KCalendarCore::Incidence::Ptr newInc;
1432     if (incidence->type() == KCalendarCore::Incidence::TypeEvent) {
1433         KCalendarCore::Event::Ptr nEvent(static_cast<KCalendarCore::Event::Ptr>(incidence)->clone());
1434         nEvent->setUid(KCalendarCore::CalFormat::createUniqueId());
1435         newInc = nEvent;
1436     } else if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
1437         KCalendarCore::Todo::Ptr nTodo(static_cast<KCalendarCore::Todo::Ptr>(incidence)->clone());
1438         nTodo->setUid(KCalendarCore::CalFormat::createUniqueId());
1439         newInc = nTodo;
1440     } else if (incidence->type() == KCalendarCore::Incidence::TypeJournal) {
1441         KCalendarCore::Journal::Ptr nJournal(static_cast<KCalendarCore::Journal::Ptr>(incidence)->clone());
1442         nJournal->setUid(KCalendarCore::CalFormat::createUniqueId());
1443         newInc = nJournal;
1444     } else {
1445         qCWarning(KORGANIZER_LOG) << "Trying to copy an incidence type that cannot be copied";
1446         return;
1447     }
1448 
1449     if (resources->addIncidence(newInc, newCal)) {
1450         incidenceAddFinished(newInc, true);
1451         KMessageBox::information(this,
1452                                  i18nc("@info", "\"%1\" was successfully copied to %2.", incidence->summary(), newCal->resourceName()),
1453                                  i18nc("@title:window", "Copying Succeeded"),
1454                                  "CalendarIncidenceCopy");
1455     } else {
1456         KMessageBox::error(this,
1457                            i18nc("@info", "Unable to copy the item \"%1\" to %2.", incidence->summary(), newCal->resourceName()),
1458                            i18nc("@title:window", "Copying Failed"));
1459     }
1460 #else
1461     Q_UNUSED(col)
1462     Q_UNUSED(item)
1463     qCDebug(KORGANIZER_LOG) << "AKONADI PORT: Disabled code in  " << Q_FUNC_INFO;
1464 #endif
1465 }
1466 
1467 void CalendarView::moveIncidenceToResource(const Akonadi::Item &item, const Akonadi::Collection &col)
1468 {
1469 #ifdef AKONADI_PORT_DISABLED
1470     if (!incidence) {
1471         qCCritical(KORGANIZER_LOG) << "Null incidence";
1472         return;
1473     }
1474 
1475     KCalendarCore::CalendarResources *const resources = KOrg::StdCalendar::self();
1476     KCalendarCore::CalendarResourceManager *const manager = resources->resourceManager();
1477 
1478     // Find the resource the incidence should be moved to
1479     ResourceCalendar *newCal = nullptr;
1480     KCalendarCore::CalendarResourceManager::iterator it;
1481     for (it = manager->begin(); it != manager->end(); ++it) {
1482         ResourceCalendar *const resource = *it;
1483         if (resource->identifier() == resourceId) {
1484             newCal = resource;
1485             break;
1486         }
1487     }
1488     if (!newCal) {
1489         return;
1490     }
1491 
1492     // Clone a new Incidence from the selected Incidence and give it a new Uid.
1493     KCalendarCore::Incidence *newInc;
1494     if (incidence->type() == KCalendarCore::Incidence::TypeEvent) {
1495         KCalendarCore::Event::Ptr nEvent = static_cast<KCalendarCore::Event::Ptr>(incidence)->clone();
1496         nEvent->setUid(KCalendarCore::CalFormat::createUniqueId());
1497         newInc = nEvent;
1498     } else if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
1499         KCalendarCore::Todo::Ptr nTodo = static_cast<KCalendarCore::Todo::Ptr>(incidence)->clone();
1500         nTodo->setUid(KCalendarCore::CalFormat::createUniqueId());
1501         newInc = nTodo;
1502     } else if (incidence->type() == KCalendarCore::Incidence::TypeJournal) {
1503         KCalendarCore::Journal::Ptr nJournal = static_cast<KCalendarCore::Journal::Ptr>(incidence)->clone();
1504         nJournal->setUid(KCalendarCore::CalFormat::createUniqueId());
1505         newInc = nJournal;
1506     } else {
1507         qCWarning(KORGANIZER_LOG) << "Trying to move an incidence type that cannot be moved";
1508         return;
1509     }
1510 
1511     if (resources->addIncidence(newInc, newCal)) {
1512         incidenceAddFinished(newInc, true);
1513         ResourceCalendar *const oldCal = resources->resource(incidence);
1514         if (!oldCal || resources->deleteIncidence(incidence)) {
1515             KMessageBox::error(this,
1516                                i18nc("@info",
1517                                      "Unable to remove the item \"%1\" from the original calendar. "
1518                                      "However, a copy of this item has been put into %2",
1519                                      incidence->summary(),
1520                                      newCal->resourceName()),
1521                                i18nc("@title:window", "Moving Failed"));
1522         } else {
1523             incidenceDeleteFinished(incidence, true);
1524             KMessageBox::information(
1525                 this,
1526                 i18nc("@info", "\"%1\" was successfully moved from %2 to %3.", incidence->summary(), oldCal->resourceName(), newCal->resourceName()),
1527                 i18nc("@title:window", "Moving Succeeded"),
1528                 "CalendarIncidenceMove");
1529         }
1530     } else {
1531         KMessageBox::error(this,
1532                            i18nc("@info",
1533                                  "Unable to add the item \"%1\" into %2. "
1534                                  "This item has not been moved.",
1535                                  incidence->summary(),
1536                                  newCal->resourceName()),
1537                            i18nc("@title:window", "Moving Failed"));
1538     }
1539 #else
1540     Q_UNUSED(col)
1541     Q_UNUSED(item)
1542     qCDebug(KORGANIZER_LOG) << "AKONADI PORT: Disabled code in  " << Q_FUNC_INFO;
1543 #endif
1544 }
1545 
1546 QDateTime CalendarView::recurrenceOnDate(KCalendarCore::Incidence::Ptr incidence, QDate displayDate)
1547 {
1548     const auto start = incidence->dateTime(KCalendarCore::IncidenceBase::RoleDisplayStart);
1549     const auto offset = start.toLocalTime().date().daysTo(displayDate);
1550     return incidence->dateTime(KCalendarCore::IncidenceBase::RoleRecurrenceStart).addDays(offset);
1551 }
1552 
1553 void CalendarView::dissociateOccurrences(const Akonadi::Item &item, QDate date)
1554 {
1555     const KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
1556 
1557     if (!incidence) {
1558         qCCritical(KORGANIZER_LOG) << "Null incidence";
1559         return;
1560     }
1561 
1562     QDateTime recurrenceId = recurrenceOnDate(incidence, date);
1563     bool isFirstOccurrence = !incidence->recurrence()->getPreviousDateTime(recurrenceId).isValid();
1564 
1565     int answer;
1566     bool doOnlyThis = false;
1567     bool doFuture = false;
1568 
1569     if (isFirstOccurrence) {
1570         answer = KMessageBox::questionTwoActions(this,
1571                                                  i18n("Do you want to dissociate "
1572                                                       "the occurrence on %1 "
1573                                                       "from the recurrence?",
1574                                                       QLocale::system().toString(date, QLocale::LongFormat)),
1575                                                  i18nc("@title:window", "KOrganizer Confirmation"),
1576                                                  KGuiItem(i18n("&Dissociate")),
1577                                                  KStandardGuiItem::cancel());
1578 
1579         doOnlyThis = (answer == KMessageBox::ButtonCode::PrimaryAction);
1580     } else {
1581         answer = KMessageBox::questionTwoActionsCancel(this,
1582                                                        i18n("Do you want to dissociate "
1583                                                             "the occurrence on %1 "
1584                                                             "from the recurrence or also "
1585                                                             "dissociate future ones?",
1586                                                             QLocale::system().toString(date, QLocale::LongFormat)),
1587                                                        i18nc("@title:window", "KOrganizer Confirmation"),
1588                                                        KGuiItem(i18n("&Only Dissociate This One")),
1589                                                        KGuiItem(i18n("&Also Dissociate Future Ones")));
1590 
1591         doOnlyThis = (answer == KMessageBox::ButtonCode::PrimaryAction);
1592         doFuture = (answer == KMessageBox::ButtonCode::SecondaryAction);
1593     }
1594 
1595     if (doOnlyThis) {
1596         dissociateOccurrence(item, recurrenceId, false);
1597     } else if (doFuture) {
1598         dissociateOccurrence(item, recurrenceId, true);
1599     }
1600 }
1601 
1602 void CalendarView::dissociateOccurrence(const Akonadi::Item &item, const QDateTime &recurrenceId, bool thisAndFuture)
1603 {
1604     const KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
1605 
1606     if (thisAndFuture) {
1607         startMultiModify(i18n("Dissociate future occurrences"));
1608     } else {
1609         startMultiModify(i18n("Dissociate occurrence"));
1610     }
1611     KCalendarCore::Incidence::Ptr newInc(KCalendarCore::Calendar::createException(incidence, recurrenceId, thisAndFuture));
1612     if (newInc) {
1613         (void)mChanger->createIncidence(newInc, item.parentCollection(), this);
1614     } else {
1615         if (thisAndFuture) {
1616             KMessageBox::error(this, i18n("Dissociating the future occurrences failed."), i18nc("@title:window", "Dissociating Failed"));
1617         } else {
1618             KMessageBox::error(this, i18n("Dissociating the occurrence failed."), i18nc("@title:window", "Dissociating Failed"));
1619         }
1620     }
1621     endMultiModify();
1622 }
1623 
1624 void CalendarView::schedule_publish(const Akonadi::Item &item)
1625 {
1626     Akonadi::Item selectedItem = item;
1627     if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1628         selectedItem = selectedIncidence();
1629     }
1630 
1631     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(selectedItem);
1632     if (incidence) {
1633         mITIPHandler->publishInformation(incidence, this);
1634     }
1635 }
1636 
1637 void CalendarView::schedule_request(const Akonadi::Item &incidence)
1638 {
1639     schedule(KCalendarCore::iTIPRequest, incidence);
1640 }
1641 
1642 void CalendarView::schedule_refresh(const Akonadi::Item &incidence)
1643 {
1644     schedule(KCalendarCore::iTIPRefresh, incidence);
1645 }
1646 
1647 void CalendarView::schedule_cancel(const Akonadi::Item &incidence)
1648 {
1649     schedule(KCalendarCore::iTIPCancel, incidence);
1650 }
1651 
1652 void CalendarView::schedule_add(const Akonadi::Item &incidence)
1653 {
1654     schedule(KCalendarCore::iTIPAdd, incidence);
1655 }
1656 
1657 void CalendarView::schedule_reply(const Akonadi::Item &incidence)
1658 {
1659     schedule(KCalendarCore::iTIPReply, incidence);
1660 }
1661 
1662 void CalendarView::schedule_counter(const Akonadi::Item &incidence)
1663 {
1664     schedule(KCalendarCore::iTIPCounter, incidence);
1665 }
1666 
1667 void CalendarView::schedule_declinecounter(const Akonadi::Item &incidence)
1668 {
1669     schedule(KCalendarCore::iTIPDeclineCounter, incidence);
1670 }
1671 
1672 void CalendarView::schedule_forward(const Akonadi::Item &item)
1673 {
1674     Akonadi::Item selectedItem = item;
1675     if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1676         selectedItem = selectedIncidence();
1677     }
1678 
1679     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(selectedItem);
1680 
1681     if (incidence) {
1682         mITIPHandler->sendAsICalendar(incidence, this);
1683     }
1684 }
1685 
1686 void CalendarView::mailFreeBusy(int daysToPublish)
1687 {
1688     Akonadi::FreeBusyManager::self()->mailFreeBusy(daysToPublish, this);
1689 }
1690 
1691 void CalendarView::uploadFreeBusy()
1692 {
1693     Akonadi::FreeBusyManager::self()->publishFreeBusy(this);
1694 }
1695 
1696 void CalendarView::schedule(KCalendarCore::iTIPMethod method, const Akonadi::Item &item)
1697 {
1698     Akonadi::Item selectedItem = item;
1699     if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1700         selectedItem = selectedIncidence();
1701     }
1702 
1703     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(selectedItem);
1704 
1705     if (incidence) {
1706         mITIPHandler->sendiTIPMessage(method, incidence, this);
1707     }
1708 }
1709 
1710 void CalendarView::openAddressbook()
1711 {
1712     auto job = new KIO::CommandLauncherJob(QStringLiteral("kaddressbook"), {}, this);
1713     job->setDesktopName(QStringLiteral("org.kde.kaddressbook"));
1714     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
1715     job->start();
1716 }
1717 
1718 bool CalendarView::isReadOnly() const
1719 {
1720     return mReadOnly;
1721 }
1722 
1723 void CalendarView::setReadOnly(bool readOnly)
1724 {
1725     if (mReadOnly != readOnly) {
1726         mReadOnly = readOnly;
1727         Q_EMIT readOnlyChanged(mReadOnly);
1728     }
1729 }
1730 
1731 void CalendarView::print()
1732 {
1733     createPrinter();
1734 
1735     KOrg::BaseView *currentView = mViewManager->currentView();
1736 
1737     CalendarSupport::CalPrinter::PrintType printType = CalendarSupport::CalPrinter::Month;
1738 
1739     KCalendarCore::Incidence::List selectedIncidences;
1740     if (currentView) {
1741         printType = currentView->printType();
1742         const Akonadi::Item::List selectedViewIncidences = currentView->selectedIncidences();
1743         for (const Akonadi::Item &item : selectedViewIncidences) {
1744             if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1745                 selectedIncidences.append(item.payload<KCalendarCore::Incidence::Ptr>());
1746             }
1747         }
1748     }
1749 
1750     KCalendarCore::DateList tmpDateList = mDateNavigator->selectedDates();
1751     mCalPrinter->print(printType, tmpDateList.first(), tmpDateList.last(), selectedIncidences);
1752 }
1753 
1754 void CalendarView::printPreview()
1755 {
1756     createPrinter();
1757 
1758     KOrg::BaseView *currentView = mViewManager->currentView();
1759 
1760     CalendarSupport::CalPrinter::PrintType printType = CalendarSupport::CalPrinter::Month;
1761 
1762     KCalendarCore::Incidence::List selectedIncidences;
1763     if (currentView) {
1764         printType = currentView->printType();
1765         const Akonadi::Item::List selectedViewIncidences = currentView->selectedIncidences();
1766         for (const Akonadi::Item &item : selectedViewIncidences) {
1767             if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1768                 selectedIncidences.append(item.payload<KCalendarCore::Incidence::Ptr>());
1769             }
1770         }
1771     }
1772 
1773     KCalendarCore::DateList tmpDateList = mDateNavigator->selectedDates();
1774     mCalPrinter->print(printType, tmpDateList.first(), tmpDateList.last(), selectedIncidences, true);
1775 }
1776 
1777 void CalendarView::exportICalendar()
1778 {
1779     QString filename =
1780         QFileDialog::getSaveFileName(this, QString(), QStringLiteral("icalout.ics"), i18n("iCalendars (*.ics)"), nullptr, QFileDialog::DontConfirmOverwrite);
1781     if (!filename.isEmpty()) {
1782         // Force correct extension
1783         if (filename.right(4) != QLatin1StringView(".ics")) {
1784             filename += QLatin1StringView(".ics");
1785         }
1786 
1787         if (QFileInfo::exists(filename)) {
1788             const int answer = KMessageBox::warningContinueCancel(this,
1789                                                                   i18n("Do you want to overwrite %1?", filename),
1790                                                                   i18nc("@title:window", "Export Calendar"),
1791                                                                   KStandardGuiItem::overwrite());
1792             if (answer == KMessageBox::Cancel) {
1793                 return;
1794             }
1795         }
1796         auto format = new KCalendarCore::ICalFormat;
1797 
1798         KCalendarCore::FileStorage storage(mCalendar, filename, format);
1799         if (!storage.save()) {
1800             QString errmess;
1801             if (format->exception()) {
1802                 errmess = KCalUtils::Stringify::errorMessage(*format->exception());
1803             } else {
1804                 errmess = i18nc("save failure cause unknown", "Reason unknown");
1805             }
1806             KMessageBox::error(this, i18nc("@info", "Cannot write iCalendar file %1. %2", filename, errmess));
1807         }
1808     }
1809 }
1810 
1811 void CalendarView::eventUpdated(const Akonadi::Item &)
1812 {
1813     // Don't call updateView here. The code, which has caused the update of the
1814     // event is responsible for updating the view.
1815     //  updateView();
1816 }
1817 
1818 void CalendarView::processMainViewSelection(const Akonadi::Item &item, const QDate &date)
1819 {
1820     if (CalendarSupport::hasIncidence(item)) {
1821         mTodoList->clearSelection();
1822     }
1823     processIncidenceSelection(item, date);
1824 }
1825 
1826 void CalendarView::processTodoListSelection(const Akonadi::Item &item, const QDate &date)
1827 {
1828     if (CalendarSupport::hasIncidence(item) && mViewManager->currentView()) {
1829         mViewManager->currentView()->clearSelection();
1830     }
1831     processIncidenceSelection(item, date);
1832 }
1833 
1834 void CalendarView::processIncidenceSelection(const Akonadi::Item &item, const QDate &date)
1835 {
1836     if (item != mSelectedIncidence || date != mSaveDate) {
1837         // This signal also must be emitted if incidence is 0
1838         Q_EMIT incidenceSelected(item, date);
1839     }
1840 
1841     if (!item.isValid()) {
1842         mSelectedIncidence = item;
1843         return;
1844     }
1845 
1846     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
1847     if (!incidence) {
1848         mSelectedIncidence = item;
1849         return;
1850     }
1851     if (item == mSelectedIncidence) {
1852         if (!incidence->recurs() || mSaveDate == date) {
1853             return;
1854         }
1855     }
1856 
1857     mSelectedIncidence = item;
1858     mSaveDate = date;
1859 
1860     bool todo = false;
1861     bool subtodo = false;
1862 
1863     const bool organizerEvents = CalendarSupport::KCalPrefs::instance()->thatIsMe(incidence->organizer().email());
1864     const bool groupEvents = !incidence->attendeeByMails(CalendarSupport::KCalPrefs::instance()->allEmails()).isNull();
1865 
1866     if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
1867         todo = true;
1868         subtodo = !incidence->relatedTo().isEmpty();
1869     }
1870     Q_EMIT todoSelected(todo);
1871     Q_EMIT subtodoSelected(subtodo);
1872     Q_EMIT organizerEventsSelected(organizerEvents);
1873     Q_EMIT groupEventsSelected(groupEvents);
1874 }
1875 
1876 void CalendarView::checkClipboard()
1877 {
1878     Q_EMIT pasteEnabled(mCalendarClipboard->pasteAvailable());
1879 }
1880 
1881 void CalendarView::showDates(const KCalendarCore::DateList &selectedDates, const QDate &preferredMonth)
1882 {
1883     mDateNavigatorContainer->selectDates(selectedDates, preferredMonth);
1884     mNavigatorBar->selectDates(selectedDates);
1885 
1886     if (mViewManager->currentView()) {
1887         updateView(selectedDates.first(), selectedDates.last(), preferredMonth, false);
1888     } else {
1889         mViewManager->showAgendaView();
1890     }
1891 }
1892 
1893 void CalendarView::editFilters()
1894 {
1895     mDialogManager->showFilterEditDialog(&mFilters);
1896 }
1897 
1898 void CalendarView::updateFilter()
1899 {
1900     QStringList filters;
1901 
1902     int pos = mFilters.indexOf(mCurrentFilter);
1903     if (pos < 0) {
1904         mCurrentFilter = nullptr;
1905     }
1906 
1907     filters << i18n("No filter");
1908     for (KCalendarCore::CalFilter *filter : std::as_const(mFilters)) {
1909         if (filter) {
1910             filters << filter->name();
1911         }
1912     }
1913 
1914     // account for the additional "No filter" at the beginning! if the
1915     // filter is not in the list, pos == -1...
1916     Q_EMIT filtersUpdated(filters, pos + 1);
1917 
1918     mCalendar->setFilter(mCurrentFilter);
1919 }
1920 
1921 void CalendarView::filterActivated(int filterNo)
1922 {
1923     KCalendarCore::CalFilter *newFilter = nullptr;
1924     if (filterNo > 0 && filterNo <= int(mFilters.count())) {
1925         newFilter = mFilters.at(filterNo - 1);
1926     }
1927     if (newFilter != mCurrentFilter) {
1928         mCurrentFilter = newFilter;
1929         mCalendar->setFilter(mCurrentFilter);
1930         mViewManager->addChange(EventViews::EventView::FilterChanged);
1931         updateView();
1932     }
1933     Q_EMIT filterChanged();
1934 }
1935 
1936 bool CalendarView::isFiltered() const
1937 {
1938     return mCurrentFilter != nullptr;
1939 }
1940 
1941 QString CalendarView::currentFilterName() const
1942 {
1943     if (mCurrentFilter) {
1944         return mCurrentFilter->name();
1945     } else {
1946         return i18n("No filter");
1947     }
1948 }
1949 
1950 void CalendarView::takeOverEvent()
1951 {
1952     const Akonadi::Item item = currentSelection();
1953     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
1954 
1955     if (incidence) {
1956         return;
1957     }
1958 
1959     incidence->setOrganizer(KCalendarCore::Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email()));
1960     incidence->recreate();
1961     incidence->setReadOnly(false);
1962 
1963     // PENDING(AKONADI_PORT) call mChanger?
1964 
1965     updateView();
1966 }
1967 
1968 void CalendarView::showIntro()
1969 {
1970     qCDebug(KORGANIZER_LOG) << "To be implemented.";
1971 }
1972 
1973 void CalendarView::showDateNavigator(bool show)
1974 {
1975     if (show) {
1976         mDateNavigatorContainer->show();
1977         mDateNavigatorContainer->updateView();
1978     } else {
1979         mDateNavigatorContainer->hide();
1980     }
1981 }
1982 
1983 void CalendarView::showTodoView(bool show)
1984 {
1985     if (show) {
1986         mTodoList->show();
1987         mTodoList->updateView();
1988     } else {
1989         mTodoList->hide();
1990     }
1991 }
1992 
1993 void CalendarView::showEventViewer(bool show)
1994 {
1995     if (show) {
1996         mEventViewerBox->show();
1997     } else {
1998         mEventViewerBox->hide();
1999     }
2000 }
2001 
2002 void CalendarView::addView(KOrg::BaseView *view)
2003 {
2004     mViewManager->addView(view);
2005 }
2006 
2007 void CalendarView::showView(KOrg::BaseView *view)
2008 {
2009     mViewManager->showView(view);
2010 }
2011 
2012 void CalendarView::addExtension(CalendarViewExtension::Factory *factory)
2013 {
2014     CalendarViewExtension *extension = factory->create(mLeftSplitter);
2015     mExtensions.append(extension);
2016     if (!mETMCollectionView) {
2017         mETMCollectionView = qobject_cast<AkonadiCollectionView *>(extension);
2018     }
2019 }
2020 
2021 void CalendarView::showLeftFrame(bool show)
2022 {
2023     if (show) {
2024         mMainSplitterSizes.clear();
2025         mLeftFrame->show();
2026         Q_EMIT calendarViewExpanded(false);
2027     } else {
2028         // mPanner splitter sizes are useless if mLeftFrame is hidden, so remember them beforehand.
2029         if (mMainSplitterSizes.isEmpty()) {
2030             mMainSplitterSizes = mPanner->sizes();
2031         }
2032 
2033         mLeftFrame->hide();
2034         Q_EMIT calendarViewExpanded(true);
2035     }
2036 }
2037 
2038 Akonadi::Item CalendarView::selectedTodo()
2039 {
2040     const Akonadi::Item item = currentSelection();
2041     if (const KCalendarCore::Todo::Ptr t = Akonadi::CalendarUtils::todo(item)) {
2042         return item;
2043     }
2044 
2045     Akonadi::Item incidence;
2046 
2047     const Akonadi::Item::List selectedIncidences = mTodoList->selectedIncidences();
2048     if (!selectedIncidences.isEmpty()) {
2049         incidence = selectedIncidences.first();
2050     }
2051     if (const KCalendarCore::Todo::Ptr t = Akonadi::CalendarUtils::todo(item)) {
2052         return item;
2053     }
2054     return {};
2055 }
2056 
2057 void CalendarView::dialogClosing(const Akonadi::Item &)
2058 {
2059 }
2060 
2061 Akonadi::Item CalendarView::currentSelection()
2062 {
2063     return mViewManager->currentSelection();
2064 }
2065 
2066 Akonadi::Item CalendarView::selectedIncidence()
2067 {
2068     Akonadi::Item item = currentSelection();
2069     if (!item.isValid()) {
2070         Akonadi::Item::List selectedIncidences = mTodoList->selectedIncidences();
2071         if (!selectedIncidences.isEmpty()) {
2072             item = selectedIncidences.first();
2073         }
2074     }
2075     return item;
2076 }
2077 
2078 void CalendarView::showIncidence()
2079 {
2080     showIncidence(selectedIncidence());
2081 }
2082 
2083 void CalendarView::editIncidence()
2084 {
2085     editIncidence(selectedIncidence());
2086 }
2087 
2088 bool CalendarView::editIncidence(Akonadi::Item::Id id)
2089 {
2090     Akonadi::Item item = mCalendar->item(id);
2091     return editIncidence(item);
2092 }
2093 
2094 bool CalendarView::showIncidence(Akonadi::Item::Id id)
2095 {
2096     Akonadi::Item item = mCalendar->item(id);
2097     if (!CalendarSupport::hasIncidence(item)) {
2098         return false;
2099     }
2100     showIncidence(item);
2101     return true;
2102 }
2103 
2104 bool CalendarView::showIncidenceContext(Akonadi::Item::Id id)
2105 {
2106     Akonadi::Item item = mCalendar->item(id);
2107     if (!CalendarSupport::hasIncidence(item)) {
2108         return false;
2109     }
2110     showIncidenceContext(item);
2111     return true;
2112 }
2113 
2114 void CalendarView::deleteIncidence()
2115 {
2116     deleteIncidence(selectedIncidence());
2117 }
2118 
2119 void CalendarView::cutIncidence(const Akonadi::Item &incidence)
2120 {
2121     Q_UNUSED(incidence)
2122     edit_cut();
2123 }
2124 
2125 void CalendarView::copyIncidence(const Akonadi::Item &incidence)
2126 {
2127     Q_UNUSED(incidence)
2128     edit_copy();
2129 }
2130 
2131 void CalendarView::pasteIncidence()
2132 {
2133     edit_paste();
2134 }
2135 
2136 void CalendarView::showIncidence(const Akonadi::Item &item)
2137 {
2138     auto eventViewer = new KOEventViewerDialog(mCalendar.data(), this);
2139     eventViewer->setIncidence(item, QDate());
2140     // Disable the Edit button for read-only Incidences.
2141     if (!mCalendar->hasRight(item, Akonadi::Collection::CanChangeItem)) {
2142         eventViewer->editButton()->setEnabled(false);
2143     }
2144 
2145     eventViewer->show();
2146 }
2147 
2148 void CalendarView::showIncidenceContext(const Akonadi::Item &item)
2149 {
2150     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
2151     if (CalendarSupport::hasEvent(item)) {
2152         if (!viewManager()->currentView()->inherits("KOEventView")) {
2153             viewManager()->showAgendaView();
2154         }
2155         // just select the appropriate date
2156         mDateNavigator->selectWeek(incidence->dtStart().toLocalTime().date());
2157         return;
2158     } else if (CalendarSupport::hasJournal(item)) {
2159         if (!viewManager()->currentView()->inherits("KOJournalView")) {
2160             viewManager()->showJournalView();
2161         }
2162     } else if (CalendarSupport::hasTodo(item)) {
2163         if (!viewManager()->currentView()->inherits("KOTodoView")) {
2164             viewManager()->showTodoView();
2165         }
2166     }
2167     Akonadi::Item::List list;
2168     list.append(item);
2169     viewManager()->currentView()->showIncidences(list, QDate());
2170 }
2171 
2172 bool CalendarView::editIncidence(const Akonadi::Item &item, bool isCounter)
2173 {
2174     Q_UNUSED(isCounter)
2175     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
2176     if (!incidence) {
2177         qCCritical(KORGANIZER_LOG) << "Null incidence";
2178         return false;
2179     }
2180 
2181     if (!mCalendar->hasRight(item, Akonadi::Collection::CanChangeItem)) {
2182         showIncidence(item);
2183         return true;
2184     }
2185 
2186     IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog(item);
2187     dialog->load(item, activeIncidenceDate()); // Show the dialog as soon as it loads the item.
2188 
2189     return true;
2190 }
2191 
2192 void CalendarView::showIncidenceByUid(const QString &uid, QDate activeDate)
2193 {
2194     const auto item = mCalendar->item(uid);
2195     if (item.isValid()) {
2196         showIncidenceContext(item);
2197         mEventViewer->setIncidence(item, activeDate);
2198     }
2199 }
2200 
2201 void CalendarView::deleteIncidenceFamily(const Akonadi::Item &item)
2202 {
2203     const auto incidence = Akonadi::CalendarUtils::incidence(item);
2204     if (!incidence) {
2205         return;
2206     }
2207     deleteChildren(item);
2208     deleteRecurringIncidence(item);
2209 }
2210 
2211 void CalendarView::deleteChildren(const Akonadi::Item &item)
2212 {
2213     const auto incidence = Akonadi::CalendarUtils::incidence(item);
2214     if (incidence && !incidence->hasRecurrenceId()) {
2215         const Akonadi::Item::List childItems = mCalendar->childItems(item.id());
2216         for (const Akonadi::Item &c : childItems) {
2217             deleteIncidenceFamily(c);
2218         }
2219     }
2220 }
2221 
2222 void CalendarView::deleteRecurringIncidence(const Akonadi::Item &todoItem)
2223 {
2224     if (!mChanger->deletedRecently(todoItem.id())) {
2225         auto incidence = Akonadi::CalendarUtils::incidence(todoItem);
2226         if (incidence->recurs()) {
2227             for (const auto &instance : mCalendar->instances(incidence)) {
2228                 (void)mChanger->deleteIncidence(mCalendar->item(instance), this);
2229             }
2230         }
2231         (void)mChanger->deleteIncidence(todoItem, this);
2232     }
2233 }
2234 
2235 int CalendarView::questionIndependentChildren(const Akonadi::Item &item)
2236 {
2237     int km;
2238     auto incidence = Akonadi::CalendarUtils::incidence(item);
2239     if (!incidence->hasRecurrenceId() && !mCalendar->childItems(item.id()).isEmpty()) {
2240         km = KMessageBox::questionTwoActionsCancel(this,
2241                                                    i18n("The item \"%1\" has sub-to-dos. "
2242                                                         "Do you want to delete just this item and "
2243                                                         "make all its sub-to-dos independent, or "
2244                                                         "delete the to-do with all its sub-to-dos?",
2245                                                         incidence->summary()),
2246                                                    i18nc("@title:window", "KOrganizer Confirmation"),
2247                                                    KGuiItem(i18n("Delete Only This")),
2248                                                    KGuiItem(i18n("Delete All")));
2249 
2250         if (km == KMessageBox::ButtonCode::SecondaryAction) {
2251             km = KMessageBox::Continue;
2252         }
2253     } else {
2254         km = msgItemDelete(item);
2255     }
2256     return km;
2257 }
2258 
2259 bool CalendarView::deleteIncidence(const Akonadi::Item &item, bool force)
2260 {
2261     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
2262     if (!incidence) {
2263         if (!force) {
2264             qCCritical(KORGANIZER_LOG) << "Null incidence";
2265         }
2266         qCCritical(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): Unable to delete, incidence is null.";
2267         return false;
2268     }
2269 
2270     if (mChanger->deletedRecently(item.id())) {
2271         // it was deleted already but the etm wasn't notified yet
2272         qCWarning(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): item with id" << item.id() << "was deleted recently, skipping";
2273         return true;
2274     }
2275 
2276     if (!mCalendar->hasRight(item, Akonadi::Collection::CanDeleteItem)) {
2277         if (!force) {
2278             KMessageBox::information(this,
2279                                      i18n("The item \"%1\" is marked read-only "
2280                                           "and cannot be deleted; it probably "
2281                                           "belongs to a read-only calendar.",
2282                                           incidence->summary()),
2283                                      i18n("Removing not possible"),
2284                                      QStringLiteral("deleteReadOnlyIncidence"));
2285         }
2286         qCWarning(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): No rights to delete item";
2287         return false;
2288     }
2289 
2290     QDate itemDate = mViewManager->currentSelectionDate();
2291 
2292     int km;
2293     if (force) {
2294         km = ItemActions::All;
2295     } else if (!incidence->recurs()) { // Non-recurring, or instance of recurring.
2296         km = questionIndependentChildren(item);
2297     } else { // Recurring incidence
2298         if (!itemDate.isValid()) {
2299             qCDebug(KORGANIZER_LOG) << "Date Not Valid";
2300             km = KMessageBox::warningContinueCancel(this,
2301                                                     i18n("The calendar item \"%1\" recurs over multiple dates; "
2302                                                          "are you sure you want to delete it "
2303                                                          "and all its recurrences?",
2304                                                          incidence->summary()),
2305                                                     i18n("KOrganizer Confirmation"),
2306                                                     KGuiItem(i18n("Delete All")));
2307         } else {
2308             QDateTime itemDateTime(itemDate, {}, Qt::LocalTime);
2309             bool isFirst = !incidence->recurrence()->getPreviousDateTime(itemDateTime).isValid();
2310             bool isLast = !incidence->recurrence()->getNextDateTime(itemDateTime).isValid();
2311 
2312             QString message;
2313             QString itemFuture(i18n("Also Delete &Future")); // QT5 was a KGuiItem
2314 
2315             if (!isFirst && !isLast) {
2316                 message = i18n(
2317                     "The calendar item \"%1\" recurs over multiple dates. "
2318                     "Do you want to delete only the current one on %2, also "
2319                     "future occurrences, or all its occurrences?",
2320                     incidence->summary(),
2321                     QLocale::system().toString(itemDate, QLocale::LongFormat));
2322             } else {
2323                 message = i18n(
2324                     "The calendar item \"%1\" recurs over multiple dates. "
2325                     "Do you want to delete only the current one on %2 "
2326                     "or all its occurrences?",
2327                     incidence->summary(),
2328                     QLocale::system().toString(itemDate, QLocale::LongFormat));
2329             }
2330 
2331             if (!(isFirst && isLast)) {
2332                 QDialogButtonBox::StandardButton returnValue = PIMMessageBox::fourBtnMsgBox(this,
2333                                                                                             QMessageBox::Warning,
2334                                                                                             message,
2335                                                                                             i18n("KOrganizer Confirmation"),
2336                                                                                             i18n("Delete C&urrent"),
2337                                                                                             itemFuture,
2338                                                                                             i18n("Delete &All"));
2339                 switch (returnValue) {
2340                 case QDialogButtonBox::Ok:
2341                     if (!mCalendar->childItems(item.id()).isEmpty()) {
2342                         km = questionIndependentChildren(item);
2343                     } else {
2344                         km = ItemActions::All;
2345                     }
2346                     break;
2347                 case QDialogButtonBox::Yes:
2348                     km = ItemActions::Current;
2349                     break;
2350                 case QDialogButtonBox::No:
2351                     km = ItemActions::AlsoFuture;
2352                     break;
2353                 case QDialogButtonBox::Cancel:
2354                 default:
2355                     km = KMessageBox::Cancel;
2356                     break;
2357                 }
2358             } else {
2359                 km = questionIndependentChildren(item);
2360             }
2361         }
2362     }
2363 
2364     KCalendarCore::Incidence::Ptr oldIncidence(incidence->clone());
2365     KCalendarCore::Recurrence *recur = incidence->recurrence();
2366 
2367     switch (km) {
2368     case ItemActions::All:
2369         startMultiModify(i18n("Delete \"%1\"", incidence->summary()));
2370         deleteChildren(item);
2371         deleteRecurringIncidence(item);
2372         endMultiModify();
2373         break;
2374 
2375     case ItemActions::Parent:
2376         startMultiModify(i18n("Delete \"%1\"", incidence->summary()));
2377         makeChildrenIndependent(item);
2378         deleteRecurringIncidence(item);
2379         endMultiModify();
2380         break;
2381 
2382     case ItemActions::Current:
2383         if (recur->allDay()) {
2384             recur->addExDate(itemDate);
2385         } else {
2386             auto itemDateTime = recur->startDateTime();
2387             itemDateTime.setDate(itemDate);
2388             recur->addExDateTime(itemDateTime);
2389         }
2390         (void)mChanger->modifyIncidence(item, oldIncidence, this);
2391         break;
2392 
2393     case ItemActions::AlsoFuture:
2394         recur->setEndDate(itemDate.addDays(-1));
2395         (void)mChanger->modifyIncidence(item, oldIncidence, this);
2396         break;
2397     }
2398     return true;
2399 }
2400 
2401 void CalendarView::purgeCompleted()
2402 {
2403     if (checkedCollections().isEmpty()) {
2404         showMessage(i18n("All calendars are unchecked in the Calendar Manager. No to-do was purged."), KMessageWidget::Warning);
2405         return;
2406     }
2407 
2408     if (mCalendar->rawTodos().isEmpty()) {
2409         showMessage(i18n("There are no completed to-dos to purge."), KMessageWidget::Information);
2410         return;
2411     }
2412 
2413     int result = KMessageBox::warningContinueCancel(this,
2414                                                     i18n("Delete all completed to-dos from checked calendars?"),
2415                                                     i18nc("@title:window", "Purge To-dos"),
2416                                                     KGuiItem(i18n("Purge"), QIcon::fromTheme(QStringLiteral("entry-delete"))));
2417 
2418     if (result == KMessageBox::Continue) {
2419         mTodoPurger->purgeCompletedTodos();
2420     }
2421 }
2422 
2423 void CalendarView::warningChangeFailed(const Akonadi::Item &item)
2424 {
2425     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
2426     if (incidence) {
2427         KMessageBox::error(this, i18nc("@info", "Unable to edit \"%1\" because it is locked by another process.", incidence->summary()));
2428     }
2429 }
2430 
2431 void CalendarView::showErrorMessage(const QString &msg)
2432 {
2433     KMessageBox::error(this, msg);
2434 }
2435 
2436 void CalendarView::addIncidenceOn(const Akonadi::Item &itemadd, const QDate &dt)
2437 {
2438     if (!CalendarSupport::hasIncidence(itemadd)) {
2439         KMessageBox::error(this, i18n("Unable to copy the item to %1.", dt.toString()), i18nc("@title:window", "Copying Failed"));
2440         return;
2441     }
2442     Akonadi::Item item = mCalendar->item(itemadd.id());
2443     if (!item.isValid()) {
2444         item = itemadd;
2445     }
2446     // Create a copy of the incidence, since the incadd doesn't belong to us.
2447     KCalendarCore::Incidence::Ptr incidence(Akonadi::CalendarUtils::incidence(item)->clone());
2448     incidence->recreate();
2449 
2450     if (const KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>()) {
2451         // Adjust date
2452         QDateTime start = event->dtStart();
2453         QDateTime end = event->dtEnd();
2454 
2455         int duration = start.daysTo(end);
2456         start.setDate(dt);
2457         end.setDate(dt.addDays(duration));
2458 
2459         event->setDtStart(start);
2460         event->setDtEnd(end);
2461     } else if (const KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>()) {
2462         QDateTime due = todo->dtDue();
2463         due.setDate(dt);
2464 
2465         todo->setDtDue(due);
2466     }
2467 
2468     (void)mChanger->createIncidence(incidence, Akonadi::Collection(), this);
2469 }
2470 
2471 void CalendarView::moveIncidenceTo(const Akonadi::Item &itemmove, QDate dt)
2472 {
2473     if (!CalendarSupport::hasIncidence(itemmove)) {
2474         KMessageBox::error(this, i18n("Unable to move the item to  %1.", dt.toString()), i18nc("@title:window", "Moving Failed"));
2475         return;
2476     }
2477     Akonadi::Item item = mCalendar->item(itemmove.id());
2478     if (!item.isValid()) {
2479         addIncidenceOn(itemmove, dt);
2480         return;
2481     }
2482     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(itemmove);
2483 
2484     KCalendarCore::Incidence::Ptr oldIncidence(incidence->clone());
2485 
2486     if (const KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>()) {
2487         // Adjust date
2488         QDateTime start = event->dtStart();
2489         QDateTime end = event->dtEnd();
2490 
2491         int duration = start.daysTo(end);
2492         start.setDate(dt);
2493         end.setDate(dt.addDays(duration));
2494 
2495         event->setDtStart(start);
2496         event->setDtEnd(end);
2497     }
2498     if (const KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>()) {
2499         QDateTime due = todo->dtDue();
2500         due.setDate(dt);
2501 
2502         todo->setDtDue(due);
2503     }
2504     (void)mChanger->modifyIncidence(itemmove, oldIncidence, this);
2505 }
2506 
2507 void CalendarView::resourcesChanged()
2508 {
2509     mViewManager->addChange(EventViews::EventView::ResourcesChanged);
2510     updateView();
2511 }
2512 
2513 bool CalendarView::eventFilter(QObject *watched, QEvent *event)
2514 {
2515     if (watched == mLeftFrame && event->type() == QEvent::Show) {
2516         mSplitterSizesValid = true;
2517     }
2518     return KOrg::CalendarViewBase::eventFilter(watched, event);
2519 }
2520 
2521 void CalendarView::updateHighlightModes()
2522 {
2523     KOrg::BaseView *view = mViewManager->currentView();
2524     if (view) {
2525         bool hiEvents;
2526         bool hiTodos;
2527         bool hiJournals;
2528 
2529         view->getHighlightMode(hiEvents, hiTodos, hiJournals);
2530         mDateNavigatorContainer->setHighlightMode(hiEvents, hiTodos, hiJournals);
2531     }
2532 }
2533 
2534 void CalendarView::selectWeek(const QDate &date, const QDate &preferredMonth)
2535 {
2536     if (KOPrefs::instance()->mWeekNumbersShowWork && mViewManager->rangeMode() == KOViewManager::WORK_WEEK_RANGE) {
2537         mDateNavigator->selectWorkWeek(date);
2538     } else {
2539         mDateNavigator->selectWeek(date, preferredMonth);
2540     }
2541 }
2542 
2543 void CalendarView::changeFullView(bool fullView)
2544 {
2545     if (mViewManager->currentView()) {
2546         if (mViewManager->currentView()->identifier() == "DefaultTodoView") {
2547             showLeftFrame(!fullView);
2548         } else if (mViewManager->currentView()->identifier() == "DefaultMonthView") {
2549             showLeftFrame(!fullView);
2550             fullView ? mNavigatorBar->show() : mNavigatorBar->hide();
2551         }
2552     }
2553 }
2554 
2555 static auto forCollection(const Akonadi::Collection &collection)
2556 {
2557     return [collection](const Akonadi::CollectionCalendar::Ptr &calendar) {
2558         return calendar->collection() == collection;
2559     };
2560 }
2561 
2562 void CalendarView::collectionSelected(const Akonadi::Collection &collection)
2563 {
2564     auto calendar = std::find_if(mEnabledCalendars.cbegin(), mEnabledCalendars.cend(), forCollection(collection));
2565     if (calendar != mEnabledCalendars.cend()) {
2566         return;
2567     }
2568 
2569     const auto newCalendar = calendarForCollection(collection);
2570     mEnabledCalendars.push_back(newCalendar);
2571     mDateNavigatorContainer->addCalendar(newCalendar);
2572     Q_EMIT calendarAdded(newCalendar);
2573 }
2574 
2575 void CalendarView::collectionDeselected(const Akonadi::Collection &collection)
2576 {
2577     auto calendarIt = std::find_if(mEnabledCalendars.begin(), mEnabledCalendars.end(), forCollection(collection));
2578     if (calendarIt == mEnabledCalendars.end()) {
2579         return;
2580     }
2581 
2582     const auto calendar = *calendarIt;
2583     mEnabledCalendars.removeOne(calendar);
2584     mDateNavigatorContainer->removeCalendar(calendar);
2585     Q_EMIT calendarRemoved(calendar);
2586 }
2587 
2588 Akonadi::Collection CalendarView::defaultCollection(const QLatin1StringView &mimeType) const
2589 {
2590     // 1. Try the view collection ( used in multi-agenda view )
2591     Akonadi::Collection collection = mCalendar->collection(mViewManager->currentView()->collectionId());
2592     bool supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1StringView("");
2593     bool hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
2594     if (collection.isValid() && supportsMimeType && hasRights) {
2595         return collection;
2596     }
2597 
2598     // 2. Try the configured default collection
2599     collection = mCalendar->collection(CalendarSupport::KCalPrefs::instance()->defaultCalendarId());
2600     supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1StringView("");
2601     hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
2602     if (collection.isValid() && supportsMimeType && hasRights) {
2603         return collection;
2604     }
2605 
2606     // 3. Try the selected collection
2607     collection = selectedCollection();
2608     supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1StringView("");
2609     hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
2610     if (collection.isValid() && supportsMimeType && hasRights) {
2611         return collection;
2612     }
2613 
2614     // 4. Try the checked collections
2615     const Akonadi::Collection::List collections = checkedCollections();
2616     for (const Akonadi::Collection &checkedCollection : collections) {
2617         supportsMimeType = checkedCollection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1StringView("");
2618         hasRights = checkedCollection.rights() & Akonadi::Collection::CanCreateItem;
2619         if (checkedCollection.isValid() && supportsMimeType && hasRights) {
2620             return checkedCollection;
2621         }
2622     }
2623 
2624     // 5. Return a invalid collection, the editor will use the first one in the combo
2625     return {};
2626 }
2627 
2628 IncidenceEditorNG::IncidenceDialog *CalendarView::createIncidenceEditor(const Akonadi::Item &item, const Akonadi::Collection &collection)
2629 {
2630     IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog(item);
2631     KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item);
2632     Q_ASSERT(incidence);
2633 
2634     if (collection.isValid()) {
2635         dialog->selectCollection(collection);
2636     } else {
2637         dialog->selectCollection(defaultCollection(incidence->mimeType()));
2638     }
2639 
2640     return dialog;
2641 }
2642 
2643 Akonadi::History *CalendarView::history() const
2644 {
2645     return mChanger->history();
2646 }
2647 
2648 void CalendarView::onCutFinished()
2649 {
2650     checkClipboard();
2651 }
2652 
2653 void CalendarView::onCheckableProxyAboutToToggle(bool newState)
2654 {
2655     // Someone unchecked a collection, save the view state now.
2656     if (!newState) {
2657         mTodoList->saveViewState();
2658         KOTodoView *todoView = mViewManager->todoView();
2659         if (todoView) {
2660             todoView->saveViewState();
2661         }
2662     }
2663 }
2664 
2665 void CalendarView::onCheckableProxyToggled(bool newState)
2666 {
2667     // Someone checked a collection, restore the view state now.
2668     if (newState) {
2669         mTodoList->restoreViewState();
2670         KOTodoView *todoView = mViewManager->todoView();
2671         if (todoView) {
2672             todoView->restoreViewState();
2673         }
2674     }
2675 }
2676 
2677 void CalendarView::onTodosPurged(bool success, int numDeleted, int numIgnored)
2678 {
2679     QString message;
2680     KMessageWidget::MessageType type = KMessageWidget::Information;
2681     if (success) {
2682         if (numDeleted == 0 && numIgnored > 0) {
2683             type = KMessageWidget::Warning;
2684             message = i18n("0 completed to-dos were purged.") + QLatin1Char('\n')
2685                 + i18np("%1 to-do was ignored because it has uncompleted or read-only children.",
2686                         "%1 to-dos were ignored because they have uncompleted or read-only children.",
2687                         numIgnored);
2688         } else if (numDeleted > 0 && numIgnored == 0) {
2689             message = i18np("%1 completed to-do was purged.", "%1 completed to-dos were purged.", numDeleted);
2690         } else if (numDeleted == 0 && numIgnored == 0) {
2691             message = i18n("There are no completed to-dos to purge.");
2692         } else {
2693             type = KMessageWidget::Warning;
2694             message = i18np("%1 completed to-do was purged.", "%1 completed to-dos were purged.", numDeleted) + QLatin1Char('\n')
2695                 + i18np("%1 to-do was ignored because it has uncompleted or read-only children.",
2696                         "%1 to-dos were ignored because they have uncompleted or read-only children.",
2697                         numIgnored);
2698         }
2699     } else {
2700         message = i18n("An error occurred while purging completed to-dos: %1", mTodoPurger->lastError());
2701         type = KMessageWidget::Error;
2702     }
2703 
2704     showMessage(message, type);
2705 }
2706 
2707 void CalendarView::showMessage(const QString &message, KMessageWidget::MessageType type)
2708 {
2709     mMessageWidget->setText(message);
2710     mMessageWidget->setMessageType(type);
2711     mMessageWidget->show();
2712 }
2713 
2714 Akonadi::Collection CalendarView::selectedCollection() const
2715 {
2716     return mETMCollectionView ? mETMCollectionView->selectedCollection() : Akonadi::Collection();
2717 }
2718 
2719 Akonadi::Collection::List CalendarView::checkedCollections() const
2720 {
2721     Akonadi::Collection::List collections;
2722     if (mETMCollectionView) {
2723         collections = mETMCollectionView->checkedCollections();
2724     }
2725 
2726     // If the default calendar is here, it should be first.
2727     int count = collections.count();
2728     Akonadi::Collection::Id id = CalendarSupport::KCalPrefs::instance()->defaultCalendarId();
2729     for (int i = 0; i < count; ++i) {
2730         if (id == collections[i].id()) {
2731             const Akonadi::Collection col = collections.takeAt(i);
2732             collections.insert(0, col);
2733             break;
2734         }
2735     }
2736 
2737     return collections;
2738 }
2739 
2740 void CalendarView::handleIncidenceCreated(const Akonadi::Item &item)
2741 {
2742     Akonadi::Collection collection = item.parentCollection();
2743     if (!collection.isValid()) {
2744         qCWarning(KORGANIZER_LOG) << "Item was creating in an invalid collection !? item id=" << item.id();
2745         return;
2746     }
2747 
2748     const bool collectionIsChecked = mETMCollectionView->isChecked(collection);
2749 
2750     if (!collectionIsChecked) {
2751         QString message;
2752         if (mETMCollectionView->isVisible()) {
2753             message = i18n(
2754                 "You created an incidence in a calendar that is currently filtered out.\n"
2755                 "On the left sidebar, enable it in the calendar manager to see the incidence.");
2756         } else {
2757             message = i18n(
2758                 "You created an incidence in a calendar that is currently filtered out.\n"
2759                 "You can enable it through the calendar manager (Settings->Sidebar->Show Calendar Manager)");
2760         }
2761 
2762         mMessageWidget->setText(message);
2763         mMessageWidget->setMessageType(KMessageWidget::Information);
2764         mMessageWidget->show();
2765     }
2766 }
2767 
2768 #include "moc_calendarview.cpp"