1 /*
2   This file is part of KOrganizer.
3 
4   SPDX-FileCopyrightText: 1997, 1998, 1999 Preston Brown <preston.brown@yale.edu>
5   SPDX-FileCopyrightText: Fester Zigterman <F.J.F.ZigtermanRustenburg@student.utwente.nl>
6   SPDX-FileCopyrightText: Ian Dawes <iadawes@globalserve.net>
7   SPDX-FileCopyrightText: Laszlo Boloni <boloni@cs.purdue.edu>
8 
9   SPDX-FileCopyrightText: 2000-2004 Cornelius Schumacher <schumacher@kde.org>
10   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
11   SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
12 
13   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
14 */
15 
16 #include "calendarview.h"
17 
18 #include "akonadicollectionview.h"
19 #include "collectiongeneralpage.h"
20 #include "datechecker.h"
21 #include "datenavigator.h"
22 #include "datenavigatorcontainer.h"
23 #include "dialog/koeventviewerdialog.h"
24 #include "kodaymatrix.h"
25 #include "kodialogmanager.h"
26 #include "koglobals.h"
27 #include "koviewmanager.h"
28 #include "pimmessagebox.h"
29 #include "prefs/koprefs.h"
30 #include "views/agendaview/koagendaview.h"
31 #include "views/monthview/monthview.h"
32 #include "views/todoview/kotodoview.h"
33 #include "widgets/navigatorbar.h"
34 
35 #include <Akonadi/AttributeFactory>
36 #include <Akonadi/CollectionIdentificationAttribute>
37 
38 #include <Akonadi/Calendar/CalendarClipboard>
39 #include <Akonadi/Calendar/FreeBusyManager>
40 #include <Akonadi/Calendar/IncidenceChanger>
41 #include <Akonadi/Calendar/TodoPurger>
42 #include <akonadi/calendar/calendarsettings.h> //krazy:exclude=camelcase this is a generated file
43 
44 #include <Akonadi/CollectionMaintenancePage>
45 #include <Akonadi/CollectionPropertiesDialog>
46 #include <Akonadi/ControlGui>
47 
48 #include <CalendarSupport/CalPrinter>
49 #include <CalendarSupport/CalendarSingleton>
50 #include <CalendarSupport/IncidenceViewer>
51 #include <CalendarSupport/KCalPrefs>
52 #include <CalendarSupport/Utils>
53 
54 #include <IncidenceEditor/IncidenceDefaults>
55 #include <IncidenceEditor/IncidenceDialog>
56 #include <IncidenceEditor/IncidenceDialogFactory>
57 #include <IncidenceEditor/IncidenceEditorSettings>
58 #include <IncidenceEditor/IndividualMailComponentFactory>
59 
60 #include <KCalendarCore/CalFilter>
61 #include <KCalendarCore/FileStorage>
62 #include <KCalendarCore/ICalFormat>
63 
64 #include <KCalUtils/DndFactory>
65 #include <KCalUtils/Stringify>
66 
67 #include <KHolidays/HolidayRegion>
68 
69 #include <PimCommonAkonadi/CollectionAclPage>
70 #include <PimCommonAkonadi/ImapAclAttribute>
71 
72 #include <KDialogJobUiDelegate>
73 #include <KIO/CommandLauncherJob>
74 #include <KMessageBox>
75 #include <KNotification>
76 
77 #include <QApplication>
78 #include <QClipboard>
79 #include <QFileDialog>
80 #include <QSplitter>
81 #include <QStackedWidget>
82 #include <QVBoxLayout>
83 
84 // Meaningful aliases for dialog box return codes.
85 enum ItemActions {
86     Cancel = KMessageBox::Cancel,  // Do nothing.
87     Current = KMessageBox::Ok,  // Selected recurrence only.
88     AlsoFuture = KMessageBox::No,  // Selected and future recurrences.
89     Parent = KMessageBox::Yes,  // Instance, but not child instances.
90     All = KMessageBox::Continue,  // Instance and child instances.
91 };
92 
93 
CalendarView(QWidget * parent)94 CalendarView::CalendarView(QWidget *parent)
95     : CalendarViewBase(parent)
96     , mSearchCollectionHelper(this)
97 {
98     Akonadi::ControlGui::widgetNeedsAkonadi(this);
99     mChanger = new Akonadi::IncidenceChanger(new IncidenceEditorNG::IndividualMailComponentFactory(this), this);
100     mChanger->setDefaultCollection(Akonadi::Collection(CalendarSupport::KCalPrefs::instance()->defaultCalendarId()));
101 
102     mChanger->setDestinationPolicy(static_cast<Akonadi::IncidenceChanger::DestinationPolicy>(KOPrefs ::instance()->destination()));
103 
104     // We reuse the EntityTreeModel from the calendar singleton to save memory.
105     // We don't reuse the entire ETMCalendar because we want a different selection model. Checking/unchecking
106     // calendars in korganizer shouldn't affect kontact's summary view
107     mCalendar = Akonadi::ETMCalendar::Ptr(new Akonadi::ETMCalendar(CalendarSupport::calendarSingleton().data()));
108 
109     mCalendar->setObjectName(QStringLiteral("KOrg Calendar"));
110     mCalendarClipboard = new Akonadi::CalendarClipboard(mCalendar, mChanger, this);
111     mITIPHandler = new Akonadi::ITIPHandler(this);
112     mITIPHandler->setCalendar(mCalendar);
113     connect(mCalendarClipboard, &Akonadi::CalendarClipboard::cutFinished, this, &CalendarView::onCutFinished);
114 
115     mChanger->setEntityTreeModel(mCalendar->entityTreeModel());
116 
117     Akonadi::AttributeFactory::registerAttribute<PimCommon::ImapAclAttribute>();
118     Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionIdentificationAttribute>();
119 
120     mViewManager = new KOViewManager(this);
121     mDialogManager = new KODialogManager(this);
122     mTodoPurger = new Akonadi::TodoPurger(this);
123     mTodoPurger->setCalendar(mCalendar);
124     mTodoPurger->setIncidenceChager(mChanger);
125     connect(mTodoPurger, &Akonadi::TodoPurger::todosPurged, this, &CalendarView::onTodosPurged);
126 
127     mReadOnly = false;
128     mSplitterSizesValid = false;
129 
130     mCalPrinter = nullptr;
131 
132     mDateNavigator = new DateNavigator(this);
133     mDateChecker = new DateChecker(this);
134 
135     auto topLayout = new QVBoxLayout(this);
136     topLayout->setContentsMargins({});
137 
138     // create the main layout frames.
139     mPanner = new QSplitter(Qt::Horizontal, this);
140     mPanner->setObjectName(QStringLiteral("CalendarView::Panner"));
141     topLayout->addWidget(mPanner);
142 
143     mLeftSplitter = new QSplitter(Qt::Vertical, mPanner);
144     mLeftSplitter->setObjectName(QStringLiteral("CalendarView::LeftFrame"));
145     // The GUI checkboxes of "show widget XYZ" are confusing when the QSplitter
146     // hides the widget magically. I know I blamed Akonadi for not showing my
147     // calendar more than once.
148     mLeftSplitter->setChildrenCollapsible(false);
149 
150     mDateNavigatorContainer = new DateNavigatorContainer(mLeftSplitter);
151     mDateNavigatorContainer->setObjectName(QStringLiteral("CalendarView::DateNavigator"));
152 
153     mTodoList = new KOTodoView(true /*sidebar*/, mLeftSplitter);
154     mTodoList->setObjectName(QStringLiteral("todolist"));
155 
156     mEventViewerBox = new QWidget(mLeftSplitter);
157     auto mEventViewerBoxVBoxLayout = new QVBoxLayout(mEventViewerBox);
158     mEventViewerBoxVBoxLayout->setContentsMargins({});
159     mEventViewerBoxVBoxLayout->setContentsMargins({});
160     mEventViewer = new CalendarSupport::IncidenceViewer(mCalendar.data(), mEventViewerBox);
161     mEventViewer->setObjectName(QStringLiteral("EventViewer"));
162     mEventViewerBoxVBoxLayout->addWidget(mEventViewer);
163 
164     auto rightBox = new QWidget(mPanner);
165     auto rightBoxVBoxLayout = new QVBoxLayout(rightBox);
166     rightBoxVBoxLayout->setContentsMargins({});
167     mNavigatorBar = new NavigatorBar(rightBox);
168     rightBoxVBoxLayout->addWidget(mNavigatorBar);
169     mRightFrame = new QStackedWidget(rightBox);
170     rightBoxVBoxLayout->addWidget(mRightFrame);
171     mMessageWidget = new CalendarSupport::MessageWidget(rightBox);
172     rightBoxVBoxLayout->addWidget(mMessageWidget);
173 
174     rightBoxVBoxLayout->setStretchFactor(mRightFrame, 1);
175 
176     mLeftFrame = mLeftSplitter;
177     mLeftFrame->installEventFilter(this);
178 
179     mChanger->setGroupwareCommunication(CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication());
180     connect(mChanger, &Akonadi::IncidenceChanger::createFinished, this, &CalendarView::slotCreateFinished);
181 
182     connect(mChanger, &Akonadi::IncidenceChanger::deleteFinished, this, &CalendarView::slotDeleteFinished);
183 
184     connect(mChanger, &Akonadi::IncidenceChanger::modifyFinished, this, &CalendarView::slotModifyFinished);
185 
186     // Signals emitted by mDateNavigator
187     connect(mDateNavigator, &DateNavigator::datesSelected, this, &CalendarView::showDates);
188 
189     connect(mDateNavigatorContainer, &DateNavigatorContainer::newEventSignal,
190             this, qOverload<const QDate &>(&CalendarView::newEvent));
191     connect(mDateNavigatorContainer, &DateNavigatorContainer::newTodoSignal,
192             this, qOverload<const QDate &>(&CalendarView::newTodo));
193     connect(mDateNavigatorContainer, &DateNavigatorContainer::newJournalSignal,
194             this, qOverload<const QDate &>(&CalendarView::newJournal));
195 
196     // Signals emitted by mNavigatorBar
197     connect(mNavigatorBar, &NavigatorBar::prevYearClicked, mDateNavigator, &DateNavigator::selectPreviousYear);
198     connect(mNavigatorBar, &NavigatorBar::nextYearClicked, mDateNavigator, &DateNavigator::selectNextYear);
199     connect(mNavigatorBar, &NavigatorBar::prevMonthClicked, this, [=](){mDateNavigator->selectPreviousMonth();});
200     connect(mNavigatorBar, &NavigatorBar::nextMonthClicked, this, [=](){mDateNavigator->selectNextMonth();});
201     connect(mNavigatorBar, &NavigatorBar::monthSelected, mDateNavigator, &DateNavigator::selectMonth);
202     connect(mNavigatorBar, &NavigatorBar::yearSelected, mDateNavigator, &DateNavigator::selectYear);
203 
204     // Signals emitted by mDateNavigatorContainer
205     connect(mDateNavigatorContainer, &DateNavigatorContainer::weekClicked, this, &CalendarView::selectWeek);
206 
207     connect(mDateNavigatorContainer, &DateNavigatorContainer::prevMonthClicked, mDateNavigator, &DateNavigator::selectPreviousMonth);
208     connect(mDateNavigatorContainer, &DateNavigatorContainer::nextMonthClicked, mDateNavigator, &DateNavigator::selectNextMonth);
209     connect(mDateNavigatorContainer, &DateNavigatorContainer::prevYearClicked, mDateNavigator, &DateNavigator::selectPreviousYear);
210     connect(mDateNavigatorContainer, &DateNavigatorContainer::nextYearClicked, mDateNavigator, &DateNavigator::selectNextYear);
211     connect(mDateNavigatorContainer, &DateNavigatorContainer::monthSelected, mDateNavigator, &DateNavigator::selectMonth);
212     connect(mDateNavigatorContainer, &DateNavigatorContainer::yearSelected, mDateNavigator, &DateNavigator::selectYear);
213     connect(mDateNavigatorContainer, &DateNavigatorContainer::goPrevious, mDateNavigator, &DateNavigator::selectPrevious);
214     connect(mDateNavigatorContainer, &DateNavigatorContainer::goNext, mDateNavigator, &DateNavigator::selectNext);
215 
216     connect(mDateNavigatorContainer,
217             &DateNavigatorContainer::datesSelected,
218             mDateNavigator,
219             qOverload<const KCalendarCore::DateList &, QDate>(&DateNavigator::selectDates));
220 
221     connect(mViewManager, &KOViewManager::datesSelected, mDateNavigator, [this](const KCalendarCore::DateList &dates) {
222         mDateNavigator->selectDates(dates);
223     });
224 
225     connect(mDateNavigatorContainer, &DateNavigatorContainer::incidenceDropped, this, &CalendarView::addIncidenceOn);
226     connect(mDateNavigatorContainer, &DateNavigatorContainer::incidenceDroppedMove, this, &CalendarView::moveIncidenceTo);
227 
228     connect(mDateChecker, &DateChecker::dayPassed, mTodoList, &BaseView::dayPassed);
229     connect(mDateChecker, &DateChecker::dayPassed, this, &CalendarView::dayPassed);
230     connect(mDateChecker, &DateChecker::dayPassed, mDateNavigatorContainer, &DateNavigatorContainer::updateToday);
231 
232     connect(this, &CalendarView::configChanged, mDateNavigatorContainer, &DateNavigatorContainer::updateConfig);
233 
234     connect(this, &CalendarView::incidenceSelected, mEventViewer, &CalendarSupport::IncidenceViewer::setIncidence);
235 
236     // TODO: do a pretty Summary,
237     const QString s = i18n(
238         "<p><em>No Item Selected</em></p>"
239         "<p>Select an event, to-do or journal entry to view its details "
240         "here.</p>");
241 
242     mEventViewer->setDefaultMessage(s);
243     mEventViewer->setWhatsThis(
244         i18n("View the details of events, journal entries or to-dos "
245              "selected in KOrganizer's main view here."));
246     mEventViewer->setIncidence(Akonadi::Item(), QDate());
247 
248     mViewManager->connectTodoView(mTodoList);
249     mViewManager->connectView(mTodoList);
250 
251     KOGlobals::self()->setHolidays(KOPrefs::instance()->mHolidays);
252 
253     connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &CalendarView::checkClipboard);
254 
255     connect(mTodoList, &BaseView::incidenceSelected, this, &CalendarView::processTodoListSelection);
256     disconnect(mTodoList, &BaseView::incidenceSelected, this, &CalendarView::processMainViewSelection);
257 
258     {
259         static bool pageRegistered = false;
260 
261         if (!pageRegistered) {
262             Akonadi::CollectionPropertiesDialog::registerPage(new CalendarSupport::CollectionGeneralPageFactory);
263             Akonadi::CollectionPropertiesDialog::registerPage(new PimCommon::CollectionAclPageFactory);
264             Akonadi::CollectionPropertiesDialog::registerPage(new Akonadi::CollectionMaintenancePageFactory);
265             pageRegistered = true;
266         }
267     }
268 
269     Akonadi::FreeBusyManager::self()->setCalendar(mCalendar);
270 
271     mCalendar->registerObserver(this);
272     mDateNavigatorContainer->setCalendar(mCalendar);
273     mTodoList->setCalendar(mCalendar);
274     mEventViewer->setCalendar(mCalendar.data());
275 }
276 
~CalendarView()277 CalendarView::~CalendarView()
278 {
279     mCalendar->unregisterObserver(this);
280     mCalendar->setFilter(nullptr); // So calendar doesn't deleted it twice
281     qDeleteAll(mFilters);
282     qDeleteAll(mExtensions);
283 
284     delete mDialogManager;
285     delete mViewManager;
286     delete mEventViewer;
287 }
288 
calendar() const289 Akonadi::ETMCalendar::Ptr CalendarView::calendar() const
290 {
291     return mCalendar;
292 }
293 
activeDate(bool fallbackToToday)294 QDate CalendarView::activeDate(bool fallbackToToday)
295 {
296     KOrg::BaseView *curView = mViewManager->currentView();
297     if (curView) {
298         if (curView->selectionStart().isValid()) {
299             return curView->selectionStart().date();
300         }
301 
302         // Try the view's selectedIncidenceDates()
303         if (!curView->selectedIncidenceDates().isEmpty()) {
304             if (curView->selectedIncidenceDates().constFirst().isValid()) {
305                 return curView->selectedIncidenceDates().constFirst();
306             }
307         }
308     }
309 
310     // When all else fails, use the navigator start date, or today.
311     if (fallbackToToday) {
312         return QDate::currentDate();
313     } else {
314         return mDateNavigator->selectedDates().constFirst();
315     }
316 }
317 
activeIncidenceDate()318 QDate CalendarView::activeIncidenceDate()
319 {
320     KOrg::BaseView *curView = mViewManager->currentView();
321     if (curView) {
322         KCalendarCore::DateList dates = curView->selectedIncidenceDates();
323         if (!dates.isEmpty()) {
324             return dates.first();
325         }
326     }
327 
328     return {};
329 }
330 
startDate()331 QDate CalendarView::startDate()
332 {
333     KCalendarCore::DateList dates = mDateNavigator->selectedDates();
334     return dates.first();
335 }
336 
endDate()337 QDate CalendarView::endDate()
338 {
339     KCalendarCore::DateList dates = mDateNavigator->selectedDates();
340     return dates.last();
341 }
342 
createPrinter()343 void CalendarView::createPrinter()
344 {
345     if (!mCalPrinter) {
346         mCalPrinter = new CalendarSupport::CalPrinter(this, mCalendar);
347         connect(this, &CalendarView::configChanged, mCalPrinter, &CalendarSupport::CalPrinter::updateConfig);
348     }
349 }
350 
saveCalendar(const QString & filename)351 bool CalendarView::saveCalendar(const QString &filename)
352 {
353     // Store back all unsaved data into calendar object
354     mViewManager->currentView()->flushView();
355 
356     KCalendarCore::FileStorage storage(mCalendar);
357     storage.setFileName(filename);
358     storage.setSaveFormat(new KCalendarCore::ICalFormat);
359 
360     return storage.save();
361 }
362 
archiveCalendar()363 void CalendarView::archiveCalendar()
364 {
365     mDialogManager->showArchiveDialog();
366 }
367 
readSettings()368 void CalendarView::readSettings()
369 {
370     // read settings from the KConfig, supplying reasonable
371     // defaults where none are to be found
372 
373     KSharedConfig::Ptr config = KSharedConfig::openConfig();
374     KConfigGroup geometryConfig(config, "KOrganizer Geometry");
375 
376     QList<int> sizes = geometryConfig.readEntry("Separator1", QList<int>());
377     if (sizes.count() != 2 || sizes.count() == sizes.count(0)) {
378         sizes << mDateNavigatorContainer->minimumSizeHint().width();
379         sizes << 300;
380     }
381     mPanner->setSizes(sizes);
382 
383     sizes = geometryConfig.readEntry("Separator2", QList<int>());
384     if (!sizes.isEmpty() && sizes.count() != sizes.count(0)) {
385         mLeftSplitter->setSizes(sizes);
386     }
387 
388     mViewManager->readSettings(config.data());
389     mTodoList->restoreLayout(config.data(), QStringLiteral("Sidebar Todo View"), true);
390 
391     readFilterSettings(config.data());
392 
393     KConfigGroup viewConfig(config, "Views");
394     int dateCount = viewConfig.readEntry("ShownDatesCount", 7);
395     if (dateCount == 7) {
396         mDateNavigator->selectWeek();
397     } else {
398         const KCalendarCore::DateList dates = mDateNavigator->selectedDates();
399         if (!dates.isEmpty()) {
400             mDateNavigator->selectDates(dates.first(), dateCount);
401         }
402     }
403 }
404 
writeSettings()405 void CalendarView::writeSettings()
406 {
407     auto config = KSharedConfig::openConfig();
408     KConfigGroup geometryConfig(config, "KOrganizer Geometry");
409 
410     QList<int> list = mMainSplitterSizes.isEmpty() ? mPanner->sizes() : mMainSplitterSizes;
411     // splitter sizes are invalid (all zero) unless we have been shown once
412     if (list.count() != list.count(0) && mSplitterSizesValid) {
413         geometryConfig.writeEntry("Separator1", list);
414     }
415 
416     list = mLeftSplitter->sizes();
417     if (list.count() != list.count(0) && mSplitterSizesValid) {
418         geometryConfig.writeEntry("Separator2", list);
419     }
420 
421     mViewManager->writeSettings(config.data());
422     mTodoList->saveLayout(config.data(), QStringLiteral("Sidebar Todo View"));
423 
424     Akonadi::CalendarSettings::self()->save();
425     KOPrefs::instance()->save();
426     CalendarSupport::KCalPrefs::instance()->save();
427 
428     writeFilterSettings(config.data());
429 
430     KConfigGroup viewConfig(config, "Views");
431     viewConfig.writeEntry("ShownDatesCount", mDateNavigator->selectedDates().count());
432 
433     config->sync();
434 }
435 
readFilterSettings(KConfig * config)436 void CalendarView::readFilterSettings(KConfig *config)
437 {
438     qDeleteAll(mFilters);
439     mFilters.clear();
440 
441     KConfigGroup generalConfig(config, "General");
442     // FIXME: Move the filter loading and saving to the CalFilter class in libkcal
443     QStringList filterList = generalConfig.readEntry("CalendarFilters", QStringList());
444     QString currentFilter = generalConfig.readEntry("Current Filter");
445 
446     QStringList::ConstIterator it = filterList.constBegin();
447     QStringList::ConstIterator end = filterList.constEnd();
448     while (it != end) {
449         auto filter = new KCalendarCore::CalFilter(*it);
450         KConfigGroup filterConfig(config, QStringLiteral("Filter_") + (*it));
451         filter->setCriteria(filterConfig.readEntry("Criteria", 0));
452         filter->setCategoryList(filterConfig.readEntry("CategoryList", QStringList()));
453         if (filter->criteria() & KCalendarCore::CalFilter::HideNoMatchingAttendeeTodos) {
454             filter->setEmailList(CalendarSupport::KCalPrefs::instance()->allEmails());
455         }
456         filter->setCompletedTimeSpan(filterConfig.readEntry("HideTodoDays", 0));
457         mFilters.append(filter);
458 
459         ++it;
460     }
461 
462     int pos = filterList.indexOf(currentFilter);
463     mCurrentFilter = nullptr;
464     if (pos >= 0) {
465         mCurrentFilter = mFilters.at(pos);
466     }
467     updateFilter();
468 }
469 
writeFilterSettings(KConfig * config)470 void CalendarView::writeFilterSettings(KConfig *config)
471 {
472     QStringList filterList;
473 
474     const QStringList oldFilterList = config->groupList().filter(QRegularExpression(QStringLiteral("^Filter_.*")));
475     // Delete Old Group
476     for (const QString &conf : oldFilterList) {
477         KConfigGroup group = config->group(conf);
478         group.deleteGroup();
479     }
480 
481     filterList.reserve(mFilters.count());
482     for (KCalendarCore::CalFilter *filter : std::as_const(mFilters)) {
483         filterList << filter->name();
484         KConfigGroup filterConfig(config, QStringLiteral("Filter_") + filter->name());
485         filterConfig.writeEntry("Criteria", filter->criteria());
486         filterConfig.writeEntry("CategoryList", filter->categoryList());
487         filterConfig.writeEntry("HideTodoDays", filter->completedTimeSpan());
488     }
489     KConfigGroup generalConfig(config, "General");
490     generalConfig.writeEntry("CalendarFilters", filterList);
491     if (mCurrentFilter) {
492         generalConfig.writeEntry("Current Filter", mCurrentFilter->name());
493     } else {
494         generalConfig.writeEntry("Current Filter", QString());
495     }
496 }
497 
goDate(QDate date)498 void CalendarView::goDate(QDate date)
499 {
500     mDateNavigator->selectDate(date);
501 }
502 
showDate(QDate date)503 void CalendarView::showDate(QDate date)
504 {
505     int dateCount = mDateNavigator->datesCount();
506     if (dateCount == 7) {
507         mDateNavigator->selectWeek(date);
508     } else {
509         mDateNavigator->selectDates(date, dateCount);
510     }
511 }
512 
goToday()513 void CalendarView::goToday()
514 {
515     mDateNavigator->selectToday();
516 }
517 
goNext()518 void CalendarView::goNext()
519 {
520     if (qobject_cast<MonthView *>(mViewManager->currentView())) {
521         const QDate month = mDateNavigatorContainer->monthOfNavigator(0);
522         QPair<QDate, QDate> limits = KODayMatrix::matrixLimits(month);
523         mDateNavigator->selectNextMonth(month, limits.first, limits.second);
524     } else {
525         mDateNavigator->selectNext();
526     }
527 }
528 
goPrevious()529 void CalendarView::goPrevious()
530 {
531     if (qobject_cast<MonthView *>(mViewManager->currentView())) {
532         const QDate month = mDateNavigatorContainer->monthOfNavigator(0);
533         QPair<QDate, QDate> limits = KODayMatrix::matrixLimits(month);
534         mDateNavigator->selectPreviousMonth(month, limits.first, limits.second);
535     } else {
536         mDateNavigator->selectPrevious();
537     }
538 }
539 
updateConfig()540 void CalendarView::updateConfig()
541 {
542     if (mCalPrinter) {
543         mCalPrinter->deleteLater();
544         mCalPrinter = nullptr;
545     }
546 
547     KOGlobals::self()->setHolidays(KOPrefs::instance()->mHolidays);
548 
549     // config changed lets tell the date navigator the new modes
550     // if there weren't changed they are ignored
551     updateHighlightModes();
552 
553     Q_EMIT configChanged();
554 
555     // switch between merged, side by side and tabbed agenda if needed
556     mViewManager->updateMultiCalendarDisplay();
557 
558     // To make the "fill window" configurations work
559     mViewManager->raiseCurrentView();
560 
561     mChanger->setDestinationPolicy(static_cast<Akonadi::IncidenceChanger::DestinationPolicy>(KOPrefs::instance()->destination()));
562 
563     mChanger->setGroupwareCommunication(CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication());
564 }
565 
slotCreateFinished(int changeId,const Akonadi::Item & item,Akonadi::IncidenceChanger::ResultCode resultCode,const QString & errorString)566 void CalendarView::slotCreateFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString)
567 {
568     Q_UNUSED(changeId)
569     if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) {
570         changeIncidenceDisplay(item, Akonadi::IncidenceChanger::ChangeTypeCreate);
571         updateUnmanagedViews();
572         checkForFilteredChange(item);
573     } else if (!errorString.isEmpty()) {
574         qCCritical(KORGANIZER_LOG) << "Incidence not added, job reported error: " << errorString;
575     }
576 }
577 
slotModifyFinished(int changeId,const Akonadi::Item & item,Akonadi::IncidenceChanger::ResultCode resultCode,const QString & errorString)578 void CalendarView::slotModifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString)
579 {
580     Q_UNUSED(changeId)
581     if (resultCode != Akonadi::IncidenceChanger::ResultCodeSuccess) {
582         qCCritical(KORGANIZER_LOG) << "Incidence not modified, job reported error: " << errorString;
583         return;
584     }
585 
586     Q_ASSERT(item.isValid());
587     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
588     Q_ASSERT(incidence);
589     QSet<KCalendarCore::IncidenceBase::Field> dirtyFields = incidence->dirtyFields();
590     incidence->resetDirtyFields();
591     // Record completed todos in journals, if enabled. we should to this here in
592     // favor of the todolist. users can mark a task as completed in an editor
593     // as well.
594     if (incidence->type() == KCalendarCore::Incidence::TypeTodo && KOPrefs::instance()->recordTodosInJournals()
595         && (dirtyFields.contains(KCalendarCore::Incidence::FieldCompleted))) {
596         KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>();
597         if (todo->isCompleted() || todo->recurs()) {
598             QString timeStr = QLocale::system().toString(QTime::currentTime(), QLocale::ShortFormat);
599             QString description = i18n("Todo completed: %1 (%2)", incidence->summary(), timeStr);
600 
601             KCalendarCore::Journal::List journals = calendar()->journals(QDate::currentDate());
602 
603             if (journals.isEmpty()) {
604                 KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
605                 journal->setDtStart(QDateTime::currentDateTime());
606 
607                 QString dateStr = QLocale::system().toString(QDate::currentDate(), QLocale::LongFormat);
608                 journal->setSummary(i18n("Journal of %1", dateStr));
609                 journal->setDescription(description);
610 
611                 if (mChanger->createIncidence(journal, item.parentCollection(), this) == -1) {
612                     qCCritical(KORGANIZER_LOG) << "Unable to add Journal";
613                     return;
614                 }
615             } else { // journal list is not empty
616                 Akonadi::Item journalItem = mCalendar->item(journals.first()->uid());
617                 KCalendarCore::Journal::Ptr journal = CalendarSupport::journal(journalItem);
618                 KCalendarCore::Journal::Ptr oldJournal(journal->clone());
619                 journal->setDescription(journal->description().append(QLatin1Char('\n') + description));
620                 (void) mChanger->modifyIncidence(journalItem, oldJournal, this);
621             }
622         }
623     }
624 
625     changeIncidenceDisplay(item, Akonadi::IncidenceChanger::ChangeTypeCreate);
626     updateUnmanagedViews();
627     checkForFilteredChange(item);
628 }
629 
slotDeleteFinished(int changeId,const QVector<Akonadi::Item::Id> & itemIdList,Akonadi::IncidenceChanger::ResultCode resultCode,const QString & errorString)630 void CalendarView::slotDeleteFinished(int changeId,
631                                       const QVector<Akonadi::Item::Id> &itemIdList,
632                                       Akonadi::IncidenceChanger::ResultCode resultCode,
633                                       const QString &errorString)
634 {
635     Q_UNUSED(changeId)
636     if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) {
637         for (Akonadi::Item::Id id : itemIdList) {
638             Akonadi::Item item = mCalendar->item(id);
639             if (item.isValid()) {
640                 changeIncidenceDisplay(item, Akonadi::IncidenceChanger::ChangeTypeDelete);
641             }
642         }
643         updateUnmanagedViews();
644     } else {
645         qCCritical(KORGANIZER_LOG) << "Incidence not deleted, job reported error: " << errorString;
646     }
647 }
648 
checkForFilteredChange(const Akonadi::Item & item)649 void CalendarView::checkForFilteredChange(const Akonadi::Item &item)
650 {
651     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
652     KCalendarCore::CalFilter *filter = calendar()->filter();
653     if (filter && !filter->filterIncidence(incidence)) {
654         // Incidence is filtered and thus not shown in the view, tell the
655         // user so that he isn't surprised if his new event doesn't show up
656         mMessageWidget->setText(
657             i18n("The item \"%1\" is filtered by your current filter rules, "
658                  "so it will be hidden and not appear in the view.",
659                  incidence->summary()));
660         mMessageWidget->show();
661     }
662 }
663 
startMultiModify(const QString & text)664 void CalendarView::startMultiModify(const QString &text)
665 {
666     mChanger->startAtomicOperation(text);
667 }
668 
endMultiModify()669 void CalendarView::endMultiModify()
670 {
671     mChanger->endAtomicOperation();
672 }
673 
changeIncidenceDisplay(const Akonadi::Item & item,Akonadi::IncidenceChanger::ChangeType changeType)674 void CalendarView::changeIncidenceDisplay(const Akonadi::Item &item, Akonadi::IncidenceChanger::ChangeType changeType)
675 {
676     if (mDateNavigatorContainer->isVisible()) {
677         mDateNavigatorContainer->updateView();
678     }
679 
680     if (CalendarSupport::hasIncidence(item)) {
681         // If there is an event view visible update the display
682         mViewManager->currentView()->changeIncidenceDisplay(item, changeType);
683     } else {
684         mViewManager->currentView()->updateView();
685     }
686 }
687 
updateView(const QDate & start,const QDate & end,const QDate & preferredMonth,const bool updateTodos)688 void CalendarView::updateView(const QDate &start, const QDate &end, const QDate &preferredMonth, const bool updateTodos)
689 {
690     const bool currentViewIsTodoView = mViewManager->currentView()->identifier() == "DefaultTodoView";
691 
692     if (updateTodos && !currentViewIsTodoView && mTodoList->isVisible()) {
693         // Update the sidepane todoView
694         mTodoList->updateView();
695     }
696 
697     if (start.isValid() && end.isValid() && !(currentViewIsTodoView && !updateTodos)) {
698         mViewManager->updateView(start, end, preferredMonth);
699     }
700 
701     if (mDateNavigatorContainer->isVisible()) {
702         mDateNavigatorContainer->updateView();
703     }
704 }
705 
updateView()706 void CalendarView::updateView()
707 {
708     const KCalendarCore::DateList tmpList = mDateNavigator->selectedDates();
709     const QDate month = mDateNavigatorContainer->monthOfNavigator();
710 
711     // We assume that the navigator only selects consecutive days.
712     updateView(tmpList.first(), tmpList.last(), month /**preferredMonth*/);
713 }
714 
updateUnmanagedViews()715 void CalendarView::updateUnmanagedViews()
716 {
717     if (mDateNavigatorContainer->isVisible()) {
718         mDateNavigatorContainer->updateDayMatrix();
719     }
720 }
721 
msgItemDelete(const Akonadi::Item & item)722 int CalendarView::msgItemDelete(const Akonadi::Item &item)
723 {
724     return KMessageBox::warningContinueCancel(
725         this,
726         i18nc("@info", "Do you really want to permanently remove the item \"%1\"?", CalendarSupport::incidence(item)->summary()),
727         i18nc("@title:window", "Delete Item?"),
728         KStandardGuiItem::del());
729 }
730 
edit_cut()731 void CalendarView::edit_cut()
732 {
733     const Akonadi::Item item = selectedIncidence();
734     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
735     if (!incidence) {
736         qCCritical(KORGANIZER_LOG) << "Null incidence";
737         return;
738     }
739 
740     mCalendarClipboard->cutIncidence(incidence, Akonadi::CalendarClipboard::AskMode);
741 }
742 
edit_copy()743 void CalendarView::edit_copy()
744 {
745     const Akonadi::Item item = selectedIncidence();
746 
747     if (!item.isValid()) {
748         KNotification::beep();
749         qCCritical(KORGANIZER_LOG) << "Invalid item";
750         return;
751     }
752 
753     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
754     Q_ASSERT(incidence);
755     if (!mCalendarClipboard->copyIncidence(incidence, Akonadi::CalendarClipboard::AskMode)) {
756         qCCritical(KORGANIZER_LOG) << "Error copying incidence";
757     }
758 
759     checkClipboard();
760 }
761 
edit_paste()762 void CalendarView::edit_paste()
763 {
764     // If in agenda and month view, use the selected time and date from there.
765     // In all other cases, use the navigator's selected date.
766 
767     QDateTime endDT;
768     QDateTime finalDateTime;
769     bool useEndTime = false;
770     KCalUtils::DndFactory::PasteFlags pasteFlags = {};
771 
772     KOrg::BaseView *curView = mViewManager->currentView();
773     KOAgendaView *agendaView = mViewManager->agendaView();
774     MonthView *monthView = mViewManager->monthView();
775 
776     if (!curView) {
777         qCWarning(KORGANIZER_LOG) << "No view is selected, can't paste";
778         return;
779     }
780 
781     if (curView == agendaView && agendaView->selectionStart().isValid()) {
782         const QDate date = agendaView->selectionStart().date();
783         endDT = agendaView->selectionEnd();
784         useEndTime = !agendaView->selectedIsSingleCell();
785         if (agendaView->selectedIsAllDay()) {
786             finalDateTime = QDateTime(date.startOfDay());
787         } else {
788             finalDateTime = QDateTime(date, agendaView->selectionStart().time());
789         }
790     } else if (curView == monthView && monthView->selectionStart().isValid()) {
791         finalDateTime = QDateTime(monthView->selectionStart().date().startOfDay());
792         pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime;
793     } else if (!mDateNavigator->selectedDates().isEmpty() && curView->supportsDateNavigation()) {
794         // default to the selected date from the navigator
795         const KCalendarCore::DateList dates = mDateNavigator->selectedDates();
796         if (!dates.isEmpty()) {
797             finalDateTime = QDateTime(dates.first().startOfDay());
798             pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime;
799         }
800     }
801 
802     if (!finalDateTime.isValid() && curView->supportsDateNavigation()) {
803         KMessageBox::sorry(this, i18n("Paste failed: unable to determine a valid target date."));
804         return;
805     }
806 
807     KCalUtils::DndFactory factory(mCalendar);
808 
809     KCalendarCore::Incidence::List pastedIncidences = factory.pasteIncidences(finalDateTime, pasteFlags);
810     KCalendarCore::Incidence::List::Iterator it;
811 
812     for (it = pastedIncidences.begin(); it != pastedIncidences.end(); ++it) {
813         // FIXME: use a visitor here
814         if ((*it)->type() == KCalendarCore::Incidence::TypeEvent) {
815             KCalendarCore::Event::Ptr pastedEvent = (*it).staticCast<KCalendarCore::Event>();
816             // only use selected area if event is of the same type (all-day or non-all-day
817             // as the current selection is
818             if (agendaView && endDT.isValid() && useEndTime) {
819                 if ((pastedEvent->allDay() && agendaView->selectedIsAllDay()) || (!pastedEvent->allDay() && !agendaView->selectedIsAllDay())) {
820                     QDateTime kdt = endDT.toLocalTime();
821                     pastedEvent->setDtEnd(kdt.toTimeZone(pastedEvent->dtEnd().timeZone()));
822                 }
823             }
824 
825             pastedEvent->setRelatedTo(QString());
826             (void) mChanger->createIncidence(KCalendarCore::Event::Ptr(pastedEvent->clone()), Akonadi::Collection(), this);
827         } else if ((*it)->type() == KCalendarCore::Incidence::TypeTodo) {
828             KCalendarCore::Todo::Ptr pastedTodo = (*it).staticCast<KCalendarCore::Todo>();
829             Akonadi::Item _selectedTodoItem = selectedTodo();
830 
831             // if we are cutting a hierarchy only the root
832             // should be son of _selectedTodo
833             KCalendarCore::Todo::Ptr _selectedTodo = CalendarSupport::todo(_selectedTodoItem);
834             if (_selectedTodo && pastedTodo->relatedTo().isEmpty()) {
835                 pastedTodo->setRelatedTo(_selectedTodo->uid());
836             }
837 
838             // When pasting multiple incidences, don't ask which collection to use, for each one
839             (void) mChanger->createIncidence(KCalendarCore::Todo::Ptr(pastedTodo->clone()), Akonadi::Collection(), this);
840         } else if ((*it)->type() == KCalendarCore::Incidence::TypeJournal) {
841             // When pasting multiple incidences, don't ask which collection to use, for each one
842             (void) mChanger->createIncidence(KCalendarCore::Incidence::Ptr((*it)->clone()), Akonadi::Collection(), this);
843         }
844     }
845 }
846 
edit_options()847 void CalendarView::edit_options()
848 {
849     mDialogManager->showOptionsDialog();
850 }
851 
nextQuarterHour(const QTime & time)852 static QTime nextQuarterHour(const QTime &time)
853 {
854     if (time.second() % 900) {
855         return time.addSecs(900 - (time.minute() * 60 + time.second()) % 900);
856     }
857     return time; // roundup not needed
858 }
859 
dateTimesForNewEvent(QDateTime & startDt,QDateTime & endDt,bool & allDay)860 void CalendarView::dateTimesForNewEvent(QDateTime &startDt, QDateTime &endDt, bool &allDay)
861 {
862     mViewManager->currentView()->eventDurationHint(startDt, endDt, allDay);
863 
864     if (!startDt.isValid() || !endDt.isValid()) {
865         startDt.setDate(activeDate(true));
866 
867         QTime prefTime;
868         if (CalendarSupport::KCalPrefs::instance()->startTime().isValid()) {
869             prefTime = CalendarSupport::KCalPrefs::instance()->startTime().time();
870         }
871 
872         const QDateTime currentDateTime = QDateTime::currentDateTime();
873         if (startDt.date() == currentDateTime.date()) {
874             // If today and the current time is already past the default time
875             // use the next quarter hour after the current time.
876             // but don't spill over into tomorrow.
877             const QTime currentTime = currentDateTime.time();
878             if (!prefTime.isValid() || (currentTime > prefTime && currentTime < QTime(23, 45))) {
879                 prefTime = nextQuarterHour(currentTime);
880             }
881         }
882         startDt.setTime(prefTime);
883 
884         int addSecs = (CalendarSupport::KCalPrefs::instance()->mDefaultDuration.time().hour() * 3600)
885             + (CalendarSupport::KCalPrefs::instance()->mDefaultDuration.time().minute() * 60);
886         endDt = startDt.addSecs(addSecs);
887     }
888 }
889 
incidenceDialog(const Akonadi::Item & item)890 IncidenceEditorNG::IncidenceDialog *CalendarView::incidenceDialog(const Akonadi::Item &item)
891 {
892     IncidenceEditorNG::IncidenceDialog *dialog = mDialogManager->createDialog(item);
893     connect(dialog, &IncidenceEditorNG::IncidenceDialog::incidenceCreated, this, &CalendarView::handleIncidenceCreated);
894     return dialog;
895 }
896 
newEventEditor(const KCalendarCore::Event::Ptr & event)897 IncidenceEditorNG::IncidenceDialog *CalendarView::newEventEditor(const KCalendarCore::Event::Ptr &event)
898 {
899     Akonadi::Item item;
900     item.setPayload(event);
901     IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog(item);
902 
903     dialog->load(item);
904 
905     mDialogManager->connectTypeAhead(dialog, qobject_cast<KOEventView *>(viewManager()->currentView()));
906 
907     return dialog;
908 }
909 
newEvent()910 void CalendarView::newEvent()
911 {
912     newEvent(QDateTime(), QDateTime());
913 }
914 
newEvent(const QDate & dt)915 void CalendarView::newEvent(const QDate &dt)
916 {
917     QDateTime startDt(dt, CalendarSupport::KCalPrefs::instance()->mStartTime.time());
918     QTime duration = CalendarSupport::KCalPrefs::instance()->defaultDuration().time();
919     QTime time = startDt.time();
920 
921     time = time.addSecs(duration.hour() * 3600 + duration.minute() * 60 + duration.second());
922     QDateTime endDt(startDt);
923     endDt.setTime(time);
924     newEvent(startDt, endDt);
925 }
926 
newEvent(const QDateTime & startDt)927 void CalendarView::newEvent(const QDateTime &startDt)
928 {
929     newEvent(startDt, startDt);
930 }
931 
newEvent(const QDateTime & startDtParam,const QDateTime & endDtParam,bool allDay)932 void CalendarView::newEvent(const QDateTime &startDtParam, const QDateTime &endDtParam, bool allDay)
933 {
934     // Let the current view change the default start/end datetime
935     QDateTime startDt(startDtParam);
936     QDateTime endDt(endDtParam);
937 
938     // Adjust the start/end date times (i.e. replace invalid values by defaults,
939     // and let the view adjust the type.
940     dateTimesForNewEvent(startDt, endDt, allDay);
941 
942     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
943     defaults.setStartDateTime(startDt);
944     defaults.setEndDateTime(endDt);
945 
946     KCalendarCore::Event::Ptr event(new KCalendarCore::Event);
947     defaults.setDefaults(event);
948     event->setAllDay(allDay);
949 
950     IncidenceEditorNG::IncidenceDialog *eventEditor = newEventEditor(event);
951     Q_ASSERT(eventEditor);
952 
953     // Fallsback to the default collection defined in config
954     eventEditor->selectCollection(defaultCollection(KCalendarCore::Event::eventMimeType()));
955 }
956 
newEvent(const QString & summary,const QString & description,const QStringList & attachments,const QStringList & attendees,const QStringList & attachmentMimetypes,bool inlineAttachment)957 void CalendarView::newEvent(const QString &summary,
958                             const QString &description,
959                             const QStringList &attachments,
960                             const QStringList &attendees,
961                             const QStringList &attachmentMimetypes,
962                             bool inlineAttachment)
963 {
964     Akonadi::Collection defaultCol = defaultCollection(KCalendarCore::Event::eventMimeType());
965 
966     IncidenceEditorNG::IncidenceDialogFactory::createEventEditor(summary,
967                                                                  description,
968                                                                  attachments,
969                                                                  attendees,
970                                                                  attachmentMimetypes,
971                                                                  QStringList() /* attachment labels */,
972                                                                  inlineAttachment,
973                                                                  defaultCol,
974                                                                  true /* cleanupAttachmentTempFiles */,
975                                                                  this /* parent */);
976 }
977 
newTodo(const QString & summary,const QString & description,const QStringList & attachments,const QStringList & attendees,const QStringList & attachmentMimetypes,bool inlineAttachment)978 void CalendarView::newTodo(const QString &summary,
979                            const QString &description,
980                            const QStringList &attachments,
981                            const QStringList &attendees,
982                            const QStringList &attachmentMimetypes,
983                            bool inlineAttachment)
984 {
985     Akonadi::Collection defaultCol = defaultCollection(KCalendarCore::Todo::todoMimeType());
986 
987     IncidenceEditorNG::IncidenceDialogFactory::createTodoEditor(summary,
988                                                                 description,
989                                                                 attachments,
990                                                                 attendees,
991                                                                 attachmentMimetypes,
992                                                                 QStringList() /* attachment labels */,
993                                                                 inlineAttachment,
994                                                                 defaultCol,
995                                                                 true /* cleanupAttachmentTempFiles */,
996                                                                 this /* parent */);
997 }
998 
newTodo()999 void CalendarView::newTodo()
1000 {
1001     newTodo(Akonadi::Collection());
1002 }
1003 
newTodo(const Akonadi::Collection & collection)1004 void CalendarView::newTodo(const Akonadi::Collection &collection)
1005 {
1006     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1007 
1008     bool allDay = true;
1009     if (mViewManager->currentView()->isEventView()) {
1010         QDateTime startDt;
1011         QDateTime endDt;
1012         dateTimesForNewEvent(startDt, endDt, allDay);
1013 
1014         defaults.setStartDateTime(startDt);
1015         defaults.setEndDateTime(endDt);
1016     }
1017 
1018     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1019     defaults.setDefaults(todo);
1020     todo->setAllDay(allDay);
1021 
1022     Akonadi::Item item;
1023     item.setPayload(todo);
1024 
1025     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1026 
1027     dialog->load(item);
1028 }
1029 
newTodo(const QDate & date)1030 void CalendarView::newTodo(const QDate &date)
1031 {
1032     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1033     defaults.setEndDateTime(QDateTime(date, QTime::currentTime()));
1034 
1035     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1036     defaults.setDefaults(todo);
1037     todo->setAllDay(true);
1038 
1039     Akonadi::Item item;
1040     item.setPayload(todo);
1041 
1042     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item);
1043     dialog->load(item);
1044 }
1045 
newJournal()1046 void CalendarView::newJournal()
1047 {
1048     newJournal(QString(), activeDate(true));
1049 }
1050 
newJournal(const QDate & date)1051 void CalendarView::newJournal(const QDate &date)
1052 {
1053     newJournal(QString(), date.isValid() ? date : activeDate(true));
1054 }
1055 
newJournal(const Akonadi::Collection & collection)1056 void CalendarView::newJournal(const Akonadi::Collection &collection)
1057 {
1058     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1059 
1060     if (mViewManager->currentView()->isEventView()) {
1061         QDateTime startDt;
1062         QDateTime endDt;
1063         bool allDay = true;
1064         dateTimesForNewEvent(startDt, endDt, allDay);
1065 
1066         defaults.setStartDateTime(startDt);
1067         defaults.setEndDateTime(endDt);
1068     }
1069 
1070     KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
1071     defaults.setDefaults(journal);
1072 
1073     Akonadi::Item item;
1074     item.setPayload(journal);
1075     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1076     dialog->load(item);
1077 }
1078 
newJournal(const QString & text,QDate date)1079 void CalendarView::newJournal(const QString &text, QDate date)
1080 {
1081     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1082 
1083     KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal);
1084     defaults.setStartDateTime(QDateTime(date.startOfDay()));
1085     defaults.setDefaults(journal);
1086 
1087     journal->setSummary(text);
1088 
1089     Akonadi::Item item;
1090     item.setPayload(journal);
1091 
1092     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item);
1093     dialog->load(item);
1094 }
1095 
currentView() const1096 KOrg::BaseView *CalendarView::currentView() const
1097 {
1098     return mViewManager->currentView();
1099 }
1100 
configureCurrentView()1101 void CalendarView::configureCurrentView()
1102 {
1103     KOrg::BaseView *const view = currentView();
1104     if (view && view->hasConfigurationDialog()) {
1105         view->showConfigurationDialog(this);
1106     }
1107 }
1108 
newSubTodo()1109 void CalendarView::newSubTodo()
1110 {
1111     const Akonadi::Item item = selectedTodo();
1112     if (CalendarSupport::hasTodo(item)) {
1113         newSubTodo(item);
1114     }
1115 }
1116 
newSubTodo(const Akonadi::Collection & collection)1117 void CalendarView::newSubTodo(const Akonadi::Collection &collection)
1118 {
1119     if (!CalendarSupport::hasTodo(selectedTodo())) {
1120         qCWarning(KORGANIZER_LOG) << "CalendarSupport::hasTodo() is false";
1121         return;
1122     }
1123 
1124     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1125     defaults.setRelatedIncidence(CalendarSupport::incidence(selectedTodo()));
1126 
1127     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1128     defaults.setDefaults(todo);
1129 
1130     Akonadi::Item item;
1131     item.setPayload(todo);
1132 
1133     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1134     dialog->load(item);
1135 }
1136 
newSubTodo(const Akonadi::Item & parentTodo)1137 void CalendarView::newSubTodo(const Akonadi::Item &parentTodo)
1138 {
1139     IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults();
1140     defaults.setRelatedIncidence(CalendarSupport::incidence(parentTodo));
1141 
1142     KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1143     defaults.setDefaults(todo);
1144 
1145     Q_ASSERT(!todo->relatedTo().isEmpty());
1146 
1147     Akonadi::Item item;
1148     item.setPayload(todo);
1149 
1150     // Don't use parentTodo.parentCollection() because that can be a search folder.
1151     Akonadi::Collection collection = mCalendar->collection(parentTodo.storageCollectionId());
1152     IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor(item, collection);
1153     dialog->load(item);
1154 }
1155 
newFloatingEvent()1156 void CalendarView::newFloatingEvent()
1157 {
1158     const QDate date = activeDate();
1159     newEvent(QDateTime(date, QTime(12, 0, 0)), QDateTime(date, QTime(12, 0, 0)), true);
1160 }
1161 
addIncidence(const QString & ical)1162 bool CalendarView::addIncidence(const QString &ical)
1163 {
1164     KCalendarCore::ICalFormat format;
1165     format.setTimeZone(mCalendar->timeZone());
1166     KCalendarCore::Incidence::Ptr incidence(format.fromString(ical));
1167     return addIncidence(incidence);
1168 }
1169 
addIncidence(const KCalendarCore::Incidence::Ptr & incidence)1170 bool CalendarView::addIncidence(const KCalendarCore::Incidence::Ptr &incidence)
1171 {
1172     return incidence ? mChanger->createIncidence(incidence, Akonadi::Collection(), this) != -1 : false;
1173 }
1174 
appointment_show()1175 void CalendarView::appointment_show()
1176 {
1177     const Akonadi::Item item = selectedIncidence();
1178     if (CalendarSupport::hasIncidence(item)) {
1179         showIncidence(item);
1180     } else {
1181         KNotification::beep();
1182     }
1183 }
1184 
appointment_edit()1185 void CalendarView::appointment_edit()
1186 {
1187     const Akonadi::Item item = selectedIncidence();
1188     if (CalendarSupport::hasIncidence(item)) {
1189         editIncidence(item);
1190     } else {
1191         KNotification::beep();
1192     }
1193 }
1194 
appointment_delete()1195 void CalendarView::appointment_delete()
1196 {
1197     const Akonadi::Item item = selectedIncidence();
1198     if (CalendarSupport::hasIncidence(item)) {
1199         deleteIncidence(item);
1200     } else {
1201         KNotification::beep();
1202     }
1203 }
1204 
todo_unsub()1205 void CalendarView::todo_unsub()
1206 {
1207     const Akonadi::Item aTodo = selectedTodo();
1208     if (incidence_unsub(aTodo)) {
1209         updateView();
1210     }
1211 }
1212 
incidence_unsub(const Akonadi::Item & item)1213 bool CalendarView::incidence_unsub(const Akonadi::Item &item)
1214 {
1215     const KCalendarCore::Incidence::Ptr inc = CalendarSupport::incidence(item);
1216 
1217     if (!inc || inc->relatedTo().isEmpty()) {
1218         qCDebug(KORGANIZER_LOG) << "Refusing to unparent this instance" << inc;
1219         return false;
1220     }
1221 
1222     for (const auto &instance : mCalendar->instances(inc)) {
1223         KCalendarCore::Incidence::Ptr oldInstance(instance->clone());
1224         instance->setRelatedTo(QString());
1225         (void) mChanger->modifyIncidence(mCalendar->item(instance), oldInstance, this);
1226     }
1227 
1228     KCalendarCore::Incidence::Ptr oldInc(inc->clone());
1229     inc->setRelatedTo(QString());
1230     (void) mChanger->modifyIncidence(item, oldInc, this);
1231 
1232     return true;
1233 }
1234 
makeSubTodosIndependent()1235 bool CalendarView::makeSubTodosIndependent()
1236 {
1237     const Akonadi::Item aTodo = selectedTodo();
1238 
1239     startMultiModify(i18n("Make sub-to-dos independent"));
1240     bool status = makeChildrenIndependent(aTodo);
1241     endMultiModify();
1242     if (status) {
1243         updateView();
1244     }
1245     return status;
1246 }
1247 
makeChildrenIndependent(const Akonadi::Item & item)1248 bool CalendarView::makeChildrenIndependent(const Akonadi::Item &item)
1249 {
1250     const KCalendarCore::Incidence::Ptr inc = CalendarSupport::incidence(item);
1251 
1252     const Akonadi::Item::List subIncs = mCalendar->childItems(item.id());
1253 
1254     if (!inc || subIncs.isEmpty()) {
1255         qCDebug(KORGANIZER_LOG) << "Refusing to  make children independent" << inc;
1256         return false;
1257     }
1258 
1259     for (const Akonadi::Item &subInc : subIncs) {
1260         incidence_unsub(subInc);
1261     }
1262 
1263     return true;
1264 }
1265 
deleteIncidence(Akonadi::Item::Id id,bool force)1266 bool CalendarView::deleteIncidence(Akonadi::Item::Id id, bool force)
1267 {
1268     Akonadi::Item item = mCalendar->item(id);
1269     if (!CalendarSupport::hasIncidence(item)) {
1270         qCCritical(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): Item does not contain incidence.";
1271         return false;
1272     }
1273     return deleteIncidence(item, force);
1274 }
1275 
toggleAlarm(const Akonadi::Item & item)1276 void CalendarView::toggleAlarm(const Akonadi::Item &item)
1277 {
1278     const KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
1279     if (!incidence) {
1280         qCCritical(KORGANIZER_LOG) << "Null incidence";
1281         return;
1282     }
1283     KCalendarCore::Incidence::Ptr oldincidence(incidence->clone());
1284 
1285     const KCalendarCore::Alarm::List alarms = incidence->alarms();
1286     KCalendarCore::Alarm::List::ConstIterator it;
1287     KCalendarCore::Alarm::List::ConstIterator end(alarms.constEnd());
1288     for (it = alarms.constBegin(); it != end; ++it) {
1289         (*it)->toggleAlarm();
1290     }
1291     if (alarms.isEmpty()) {
1292         // Add an alarm if it didn't have one
1293         KCalendarCore::Alarm::Ptr alm = incidence->newAlarm();
1294         CalendarSupport::createAlarmReminder(alm, incidence->type());
1295     }
1296     mChanger->startAtomicOperation(i18n("Toggle Reminder"));
1297     (void) mChanger->modifyIncidence(item, oldincidence, this);
1298     mChanger->endAtomicOperation();
1299 }
1300 
toggleTodoCompleted(const Akonadi::Item & todoItem)1301 void CalendarView::toggleTodoCompleted(const Akonadi::Item &todoItem)
1302 {
1303     const KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(todoItem);
1304 
1305     if (!incidence) {
1306         qCCritical(KORGANIZER_LOG) << "Null incidence";
1307         return;
1308     }
1309     if (incidence->type() != KCalendarCore::Incidence::TypeTodo) {
1310         qCDebug(KORGANIZER_LOG) << "called for a non-Todo incidence";
1311         return;
1312     }
1313 
1314     KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(todoItem);
1315     Q_ASSERT(todo);
1316     KCalendarCore::Todo::Ptr oldtodo(todo->clone());
1317 
1318     if (todo->isCompleted()) {
1319         todo->setPercentComplete(0);
1320         todo->setCompleted(false);
1321         todo->setStatus(KCalendarCore::Incidence::StatusNone);
1322     } else {
1323         todo->setPercentComplete(0);
1324         todo->setCompleted(QDateTime::currentDateTime());
1325         todo->setStatus(KCalendarCore::Incidence::StatusCompleted);
1326     }
1327 
1328     mChanger->startAtomicOperation(i18n("Toggle To-do Completed"));
1329     (void) mChanger->modifyIncidence(todoItem, oldtodo, this);
1330     mChanger->endAtomicOperation();
1331 }
1332 
copyIncidenceToResource(const Akonadi::Item & item,const Akonadi::Collection & col)1333 void CalendarView::copyIncidenceToResource(const Akonadi::Item &item, const Akonadi::Collection &col)
1334 {
1335 #ifdef AKONADI_PORT_DISABLED
1336     if (!incidence) {
1337         qCCritical(KORGANIZER_LOG) << "Null incidence";
1338         return;
1339     }
1340 
1341     KCalendarCore::CalendarResources *const resources = KOrg::StdCalendar::self();
1342     KCalendarCore::CalendarResourceManager *const manager = resources->resourceManager();
1343 
1344     // Find the resource the incidence should be copied to
1345     ResourceCalendar *newCal = nullptr;
1346     KCalendarCore::CalendarResourceManager::iterator it;
1347     for (it = manager->begin(); it != manager->end(); ++it) {
1348         ResourceCalendar *const resource = *it;
1349         if (resource->identifier() == resourceId) {
1350             newCal = resource;
1351             break;
1352         }
1353     }
1354     if (!newCal) {
1355         return;
1356     }
1357 
1358     // Clone a new Incidence from the selected Incidence and give it a new Uid.
1359     KCalendarCore::Incidence::Ptr newInc;
1360     if (incidence->type() == KCalendarCore::Incidence::TypeEvent) {
1361         KCalendarCore::Event::Ptr nEvent(static_cast<KCalendarCore::Event::Ptr>(incidence)->clone());
1362         nEvent->setUid(KCalendarCore::CalFormat::createUniqueId());
1363         newInc = nEvent;
1364     } else if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
1365         KCalendarCore::Todo::Ptr nTodo(static_cast<KCalendarCore::Todo::Ptr>(incidence)->clone());
1366         nTodo->setUid(KCalendarCore::CalFormat::createUniqueId());
1367         newInc = nTodo;
1368     } else if (incidence->type() == KCalendarCore::Incidence::TypeJournal) {
1369         KCalendarCore::Journal::Ptr nJournal(static_cast<KCalendarCore::Journal::Ptr>(incidence)->clone());
1370         nJournal->setUid(KCalendarCore::CalFormat::createUniqueId());
1371         newInc = nJournal;
1372     } else {
1373         qCWarning(KORGANIZER_LOG) << "Trying to copy an incidence type that cannot be copied";
1374         return;
1375     }
1376 
1377     if (resources->addIncidence(newInc, newCal)) {
1378         incidenceAddFinished(newInc, true);
1379         KMessageBox::information(this,
1380                                  i18nc("@info", "\"%1\" was successfully copied to %2.", incidence->summary(), newCal->resourceName()),
1381                                  i18nc("@title:window", "Copying Succeeded"),
1382                                  "CalendarIncidenceCopy");
1383     } else {
1384         KMessageBox::error(this,
1385                            i18nc("@info", "Unable to copy the item \"%1\" to %2.", incidence->summary(), newCal->resourceName()),
1386                            i18nc("@title:window", "Copying Failed"));
1387     }
1388 #else
1389     Q_UNUSED(col)
1390     Q_UNUSED(item)
1391     qCDebug(KORGANIZER_LOG) << "AKONADI PORT: Disabled code in  " << Q_FUNC_INFO;
1392 #endif
1393 }
1394 
moveIncidenceToResource(const Akonadi::Item & item,const Akonadi::Collection & col)1395 void CalendarView::moveIncidenceToResource(const Akonadi::Item &item, const Akonadi::Collection &col)
1396 {
1397 #ifdef AKONADI_PORT_DISABLED
1398     if (!incidence) {
1399         qCCritical(KORGANIZER_LOG) << "Null incidence";
1400         return;
1401     }
1402 
1403     KCalendarCore::CalendarResources *const resources = KOrg::StdCalendar::self();
1404     KCalendarCore::CalendarResourceManager *const manager = resources->resourceManager();
1405 
1406     // Find the resource the incidence should be moved to
1407     ResourceCalendar *newCal = nullptr;
1408     KCalendarCore::CalendarResourceManager::iterator it;
1409     for (it = manager->begin(); it != manager->end(); ++it) {
1410         ResourceCalendar *const resource = *it;
1411         if (resource->identifier() == resourceId) {
1412             newCal = resource;
1413             break;
1414         }
1415     }
1416     if (!newCal) {
1417         return;
1418     }
1419 
1420     // Clone a new Incidence from the selected Incidence and give it a new Uid.
1421     KCalendarCore::Incidence *newInc;
1422     if (incidence->type() == KCalendarCore::Incidence::TypeEvent) {
1423         KCalendarCore::Event::Ptr nEvent = static_cast<KCalendarCore::Event::Ptr>(incidence)->clone();
1424         nEvent->setUid(KCalendarCore::CalFormat::createUniqueId());
1425         newInc = nEvent;
1426     } else if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
1427         KCalendarCore::Todo::Ptr nTodo = static_cast<KCalendarCore::Todo::Ptr>(incidence)->clone();
1428         nTodo->setUid(KCalendarCore::CalFormat::createUniqueId());
1429         newInc = nTodo;
1430     } else if (incidence->type() == KCalendarCore::Incidence::TypeJournal) {
1431         KCalendarCore::Journal::Ptr nJournal = static_cast<KCalendarCore::Journal::Ptr>(incidence)->clone();
1432         nJournal->setUid(KCalendarCore::CalFormat::createUniqueId());
1433         newInc = nJournal;
1434     } else {
1435         qCWarning(KORGANIZER_LOG) << "Trying to move an incidence type that cannot be moved";
1436         return;
1437     }
1438 
1439     if (resources->addIncidence(newInc, newCal)) {
1440         incidenceAddFinished(newInc, true);
1441         ResourceCalendar *const oldCal = resources->resource(incidence);
1442         if (!oldCal || resources->deleteIncidence(incidence)) {
1443             KMessageBox::error(this,
1444                                i18nc("@info",
1445                                      "Unable to remove the item \"%1\" from %2. "
1446                                      "However, a copy of this item has been put into %3.",
1447                                      incidence->summary(),
1448                                      oldCal->resourceName(),
1449                                      newCal->resourceName()),
1450                                i18nc("@title:window", "Moving Failed"));
1451         } else {
1452             incidenceDeleteFinished(incidence, true);
1453             KMessageBox::information(
1454                 this,
1455                 i18nc("@info", "\"%1\" was successfully moved from %2 to %3.", incidence->summary(), oldCal->resourceName(), newCal->resourceName()),
1456                 i18nc("@title:window", "Moving Succeeded"),
1457                 "CalendarIncidenceMove");
1458         }
1459     } else {
1460         KMessageBox::error(this,
1461                            i18nc("@info",
1462                                  "Unable to add the item \"%1\" into %2. "
1463                                  "This item has not been moved.",
1464                                  incidence->summary(),
1465                                  newCal->resourceName()),
1466                            i18nc("@title:window", "Moving Failed"));
1467     }
1468 #else
1469     Q_UNUSED(col)
1470     Q_UNUSED(item)
1471     qCDebug(KORGANIZER_LOG) << "AKONADI PORT: Disabled code in  " << Q_FUNC_INFO;
1472 #endif
1473 }
1474 
dissociateOccurrences(const Akonadi::Item & item,QDate date)1475 void CalendarView::dissociateOccurrences(const Akonadi::Item &item, QDate date)
1476 {
1477     const KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
1478 
1479     if (!incidence) {
1480         qCCritical(KORGANIZER_LOG) << "Null incidence";
1481         return;
1482     }
1483 
1484     QDateTime thisDateTime(date, {}, Qt::LocalTime);
1485     bool isFirstOccurrence = !incidence->recurrence()->getPreviousDateTime(thisDateTime).isValid();
1486 
1487     int answer;
1488     bool doOnlyThis = false;
1489     bool doFuture = false;
1490 
1491     if (isFirstOccurrence) {
1492         answer = KMessageBox::questionYesNo(this,
1493                                             i18n("Do you want to dissociate "
1494                                                  "the occurrence on %1 "
1495                                                  "from the recurrence?",
1496                                                  QLocale::system().toString(date, QLocale::LongFormat)),
1497                                             i18n("KOrganizer Confirmation"),
1498                                             KGuiItem(i18n("&Dissociate")),
1499                                             KStandardGuiItem::cancel());
1500 
1501         doOnlyThis = (answer == KMessageBox::Yes);
1502     } else {
1503         answer = KMessageBox::questionYesNoCancel(this,
1504                                                   i18n("Do you want to dissociate "
1505                                                        "the occurrence on %1 "
1506                                                        "from the recurrence or also "
1507                                                        "dissociate future ones?",
1508                                                        QLocale::system().toString(date, QLocale::LongFormat)),
1509                                                   i18n("KOrganizer Confirmation"),
1510                                                   KGuiItem(i18n("&Only Dissociate This One")),
1511                                                   KGuiItem(i18n("&Also Dissociate Future Ones")));
1512 
1513         doOnlyThis = (answer == KMessageBox::Yes);
1514         doFuture = (answer == KMessageBox::No);
1515     }
1516 
1517     if (doOnlyThis) {
1518         dissociateOccurrence(item, date, false);
1519     } else if (doFuture) {
1520         dissociateOccurrence(item, date, true);
1521     }
1522 }
1523 
dissociateOccurrence(const Akonadi::Item & item,const QDate & date,bool thisAndFuture)1524 void CalendarView::dissociateOccurrence(const Akonadi::Item &item, const QDate &date, bool thisAndFuture)
1525 {
1526     const KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
1527 
1528     if (thisAndFuture) {
1529         startMultiModify(i18n("Dissociate future occurrences"));
1530     } else {
1531         startMultiModify(i18n("Dissociate occurrence"));
1532     }
1533     QDateTime occurrenceDate = incidence->dtStart();
1534     occurrenceDate.setDate(date);
1535     qCDebug(KORGANIZER_LOG) << "create exception: " << occurrenceDate;
1536     KCalendarCore::Incidence::Ptr newInc(KCalendarCore::Calendar::createException(incidence, occurrenceDate, thisAndFuture));
1537     if (newInc) {
1538         (void) mChanger->createIncidence(newInc, item.parentCollection(), this);
1539     } else {
1540         if (thisAndFuture) {
1541             KMessageBox::sorry(this, i18n("Dissociating the future occurrences failed."), i18n("Dissociating Failed"));
1542         } else {
1543             KMessageBox::sorry(this, i18n("Dissociating the occurrence failed."), i18n("Dissociating Failed"));
1544         }
1545     }
1546     endMultiModify();
1547 }
1548 
schedule_publish(const Akonadi::Item & item)1549 void CalendarView::schedule_publish(const Akonadi::Item &item)
1550 {
1551     Akonadi::Item selectedItem = item;
1552     if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1553         selectedItem = selectedIncidence();
1554     }
1555 
1556     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(selectedItem);
1557     if (incidence) {
1558         mITIPHandler->publishInformation(incidence, this);
1559     }
1560 }
1561 
schedule_request(const Akonadi::Item & incidence)1562 void CalendarView::schedule_request(const Akonadi::Item &incidence)
1563 {
1564     schedule(KCalendarCore::iTIPRequest, incidence);
1565 }
1566 
schedule_refresh(const Akonadi::Item & incidence)1567 void CalendarView::schedule_refresh(const Akonadi::Item &incidence)
1568 {
1569     schedule(KCalendarCore::iTIPRefresh, incidence);
1570 }
1571 
schedule_cancel(const Akonadi::Item & incidence)1572 void CalendarView::schedule_cancel(const Akonadi::Item &incidence)
1573 {
1574     schedule(KCalendarCore::iTIPCancel, incidence);
1575 }
1576 
schedule_add(const Akonadi::Item & incidence)1577 void CalendarView::schedule_add(const Akonadi::Item &incidence)
1578 {
1579     schedule(KCalendarCore::iTIPAdd, incidence);
1580 }
1581 
schedule_reply(const Akonadi::Item & incidence)1582 void CalendarView::schedule_reply(const Akonadi::Item &incidence)
1583 {
1584     schedule(KCalendarCore::iTIPReply, incidence);
1585 }
1586 
schedule_counter(const Akonadi::Item & incidence)1587 void CalendarView::schedule_counter(const Akonadi::Item &incidence)
1588 {
1589     schedule(KCalendarCore::iTIPCounter, incidence);
1590 }
1591 
schedule_declinecounter(const Akonadi::Item & incidence)1592 void CalendarView::schedule_declinecounter(const Akonadi::Item &incidence)
1593 {
1594     schedule(KCalendarCore::iTIPDeclineCounter, incidence);
1595 }
1596 
schedule_forward(const Akonadi::Item & item)1597 void CalendarView::schedule_forward(const Akonadi::Item &item)
1598 {
1599     Akonadi::Item selectedItem = item;
1600     if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1601         selectedItem = selectedIncidence();
1602     }
1603 
1604     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(selectedItem);
1605 
1606     if (incidence) {
1607         mITIPHandler->sendAsICalendar(incidence, this);
1608     }
1609 }
1610 
mailFreeBusy(int daysToPublish)1611 void CalendarView::mailFreeBusy(int daysToPublish)
1612 {
1613     Akonadi::FreeBusyManager::self()->mailFreeBusy(daysToPublish, this);
1614 }
1615 
uploadFreeBusy()1616 void CalendarView::uploadFreeBusy()
1617 {
1618     Akonadi::FreeBusyManager::self()->publishFreeBusy(this);
1619 }
1620 
schedule(KCalendarCore::iTIPMethod method,const Akonadi::Item & item)1621 void CalendarView::schedule(KCalendarCore::iTIPMethod method, const Akonadi::Item &item)
1622 {
1623     Akonadi::Item selectedItem = item;
1624     if (!item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1625         selectedItem = selectedIncidence();
1626     }
1627 
1628     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(selectedItem);
1629 
1630     if (incidence) {
1631         mITIPHandler->sendiTIPMessage(method, incidence, this);
1632     }
1633 }
1634 
openAddressbook()1635 void CalendarView::openAddressbook()
1636 {
1637     auto job = new KIO::CommandLauncherJob(QStringLiteral("kaddressbook"), {}, this);
1638     job->setDesktopName(QStringLiteral("org.kde.kaddressbook"));
1639     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
1640     job->start();
1641 }
1642 
isReadOnly() const1643 bool CalendarView::isReadOnly() const
1644 {
1645     return mReadOnly;
1646 }
1647 
setReadOnly(bool readOnly)1648 void CalendarView::setReadOnly(bool readOnly)
1649 {
1650     if (mReadOnly != readOnly) {
1651         mReadOnly = readOnly;
1652         Q_EMIT readOnlyChanged(mReadOnly);
1653     }
1654 }
1655 
print()1656 void CalendarView::print()
1657 {
1658     createPrinter();
1659 
1660     KOrg::BaseView *currentView = mViewManager->currentView();
1661 
1662     CalendarSupport::CalPrinter::PrintType printType = CalendarSupport::CalPrinter::Month;
1663 
1664     KCalendarCore::Incidence::List selectedIncidences;
1665     if (currentView) {
1666         printType = currentView->printType();
1667         const Akonadi::Item::List selectedViewIncidences = currentView->selectedIncidences();
1668         for (const Akonadi::Item &item : selectedViewIncidences) {
1669             if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1670                 selectedIncidences.append(item.payload<KCalendarCore::Incidence::Ptr>());
1671             }
1672         }
1673     }
1674 
1675     KCalendarCore::DateList tmpDateList = mDateNavigator->selectedDates();
1676     mCalPrinter->print(printType, tmpDateList.first(), tmpDateList.last(), selectedIncidences);
1677 }
1678 
printPreview()1679 void CalendarView::printPreview()
1680 {
1681     createPrinter();
1682 
1683     KOrg::BaseView *currentView = mViewManager->currentView();
1684 
1685     CalendarSupport::CalPrinter::PrintType printType = CalendarSupport::CalPrinter::Month;
1686 
1687     KCalendarCore::Incidence::List selectedIncidences;
1688     if (currentView) {
1689         printType = currentView->printType();
1690         const Akonadi::Item::List selectedViewIncidences = currentView->selectedIncidences();
1691         for (const Akonadi::Item &item : selectedViewIncidences) {
1692             if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
1693                 selectedIncidences.append(item.payload<KCalendarCore::Incidence::Ptr>());
1694             }
1695         }
1696     }
1697 
1698     KCalendarCore::DateList tmpDateList = mDateNavigator->selectedDates();
1699     mCalPrinter->print(printType, tmpDateList.first(), tmpDateList.last(), selectedIncidences, true);
1700 }
1701 
exportICalendar()1702 void CalendarView::exportICalendar()
1703 {
1704     QString filename =
1705         QFileDialog::getSaveFileName(this, QString(), QStringLiteral("icalout.ics"), i18n("iCalendars (*.ics)"), nullptr, QFileDialog::DontConfirmOverwrite);
1706     if (!filename.isEmpty()) {
1707         // Force correct extension
1708         if (filename.right(4) != QLatin1String(".ics")) {
1709             filename += QLatin1String(".ics");
1710         }
1711 
1712         if (QFileInfo::exists(filename)) {
1713             const int answer = KMessageBox::warningContinueCancel(this,
1714                                                                   i18n("Do you want to overwrite %1?", filename),
1715                                                                   i18nc("@title:window", "Export Calendar"),
1716                                                                   KStandardGuiItem::overwrite());
1717             if (answer == KMessageBox::Cancel) {
1718                 return;
1719             }
1720         }
1721         auto format = new KCalendarCore::ICalFormat;
1722 
1723         KCalendarCore::FileStorage storage(mCalendar, filename, format);
1724         if (!storage.save()) {
1725             QString errmess;
1726             if (format->exception()) {
1727                 errmess = KCalUtils::Stringify::errorMessage(*format->exception());
1728             } else {
1729                 errmess = i18nc("save failure cause unknown", "Reason unknown");
1730             }
1731             KMessageBox::error(this, i18nc("@info", "Cannot write iCalendar file %1. %2", filename, errmess));
1732         }
1733     }
1734 }
1735 
eventUpdated(const Akonadi::Item &)1736 void CalendarView::eventUpdated(const Akonadi::Item &)
1737 {
1738     // Don't call updateView here. The code, which has caused the update of the
1739     // event is responsible for updating the view.
1740     //  updateView();
1741 }
1742 
processMainViewSelection(const Akonadi::Item & item,const QDate & date)1743 void CalendarView::processMainViewSelection(const Akonadi::Item &item, const QDate &date)
1744 {
1745     if (CalendarSupport::hasIncidence(item)) {
1746         mTodoList->clearSelection();
1747     }
1748     processIncidenceSelection(item, date);
1749 }
1750 
processTodoListSelection(const Akonadi::Item & item,const QDate & date)1751 void CalendarView::processTodoListSelection(const Akonadi::Item &item, const QDate &date)
1752 {
1753     if (CalendarSupport::hasIncidence(item) && mViewManager->currentView()) {
1754         mViewManager->currentView()->clearSelection();
1755     }
1756     processIncidenceSelection(item, date);
1757 }
1758 
processIncidenceSelection(const Akonadi::Item & item,const QDate & date)1759 void CalendarView::processIncidenceSelection(const Akonadi::Item &item, const QDate &date)
1760 {
1761     if (item != mSelectedIncidence || date != mSaveDate) {
1762         // This signal also must be emitted if incidence is 0
1763         Q_EMIT incidenceSelected(item, date);
1764     }
1765 
1766     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
1767     if (!incidence) {
1768         mSelectedIncidence = item;
1769         return;
1770     }
1771     if (item == mSelectedIncidence) {
1772         if (!incidence->recurs() || mSaveDate == date) {
1773             return;
1774         }
1775     }
1776 
1777     mSelectedIncidence = item;
1778     mSaveDate = date;
1779 
1780     bool todo = false;
1781     bool subtodo = false;
1782 
1783     const bool organizerEvents = CalendarSupport::KCalPrefs::instance()->thatIsMe(incidence->organizer().email());
1784     const bool groupEvents = !incidence->attendeeByMails(CalendarSupport::KCalPrefs::instance()->allEmails()).isNull();
1785 
1786     if (incidence->type() == KCalendarCore::Incidence::TypeTodo) {
1787         todo = true;
1788         subtodo = !incidence->relatedTo().isEmpty();
1789     }
1790     Q_EMIT todoSelected(todo);
1791     Q_EMIT subtodoSelected(subtodo);
1792     Q_EMIT organizerEventsSelected(organizerEvents);
1793     Q_EMIT groupEventsSelected(groupEvents);
1794 }
1795 
checkClipboard()1796 void CalendarView::checkClipboard()
1797 {
1798     Q_EMIT pasteEnabled(mCalendarClipboard->pasteAvailable());
1799 }
1800 
showDates(const KCalendarCore::DateList & selectedDates,const QDate & preferredMonth)1801 void CalendarView::showDates(const KCalendarCore::DateList &selectedDates, const QDate &preferredMonth)
1802 {
1803     mDateNavigatorContainer->selectDates(selectedDates, preferredMonth);
1804     mNavigatorBar->selectDates(selectedDates);
1805 
1806     if (mViewManager->currentView()) {
1807         updateView(selectedDates.first(), selectedDates.last(), preferredMonth, false);
1808     } else {
1809         mViewManager->showAgendaView();
1810     }
1811 }
1812 
editFilters()1813 void CalendarView::editFilters()
1814 {
1815     mDialogManager->showFilterEditDialog(&mFilters);
1816 }
1817 
updateFilter()1818 void CalendarView::updateFilter()
1819 {
1820     QStringList filters;
1821 
1822     int pos = mFilters.indexOf(mCurrentFilter);
1823     if (pos < 0) {
1824         mCurrentFilter = nullptr;
1825     }
1826 
1827     filters << i18n("No filter");
1828     for (KCalendarCore::CalFilter *filter : std::as_const(mFilters)) {
1829         if (filter) {
1830             filters << filter->name();
1831         }
1832     }
1833 
1834     // account for the additional "No filter" at the beginning! if the
1835     // filter is not in the list, pos == -1...
1836     Q_EMIT filtersUpdated(filters, pos + 1);
1837 
1838     mCalendar->setFilter(mCurrentFilter);
1839 }
1840 
filterActivated(int filterNo)1841 void CalendarView::filterActivated(int filterNo)
1842 {
1843     KCalendarCore::CalFilter *newFilter = nullptr;
1844     if (filterNo > 0 && filterNo <= int(mFilters.count())) {
1845         newFilter = mFilters.at(filterNo - 1);
1846     }
1847     if (newFilter != mCurrentFilter) {
1848         mCurrentFilter = newFilter;
1849         mCalendar->setFilter(mCurrentFilter);
1850         mViewManager->addChange(EventViews::EventView::FilterChanged);
1851         updateView();
1852     }
1853     Q_EMIT filterChanged();
1854 }
1855 
isFiltered() const1856 bool CalendarView::isFiltered() const
1857 {
1858     return mCurrentFilter != nullptr;
1859 }
1860 
currentFilterName() const1861 QString CalendarView::currentFilterName() const
1862 {
1863     if (mCurrentFilter) {
1864         return mCurrentFilter->name();
1865     } else {
1866         return i18n("No filter");
1867     }
1868 }
1869 
takeOverEvent()1870 void CalendarView::takeOverEvent()
1871 {
1872     const Akonadi::Item item = currentSelection();
1873     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
1874 
1875     if (incidence) {
1876         return;
1877     }
1878 
1879     incidence->setOrganizer(KCalendarCore::Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email()));
1880     incidence->recreate();
1881     incidence->setReadOnly(false);
1882 
1883     // PENDING(AKONADI_PORT) call mChanger?
1884 
1885     updateView();
1886 }
1887 
showIntro()1888 void CalendarView::showIntro()
1889 {
1890     qCDebug(KORGANIZER_LOG) << "To be implemented.";
1891 }
1892 
showDateNavigator(bool show)1893 void CalendarView::showDateNavigator(bool show)
1894 {
1895     if (show) {
1896         mDateNavigatorContainer->show();
1897         mDateNavigatorContainer->updateView();
1898     } else {
1899         mDateNavigatorContainer->hide();
1900     }
1901 }
1902 
showTodoView(bool show)1903 void CalendarView::showTodoView(bool show)
1904 {
1905     if (show) {
1906         mTodoList->show();
1907         mTodoList->updateView();
1908     } else {
1909         mTodoList->hide();
1910     }
1911 }
1912 
showEventViewer(bool show)1913 void CalendarView::showEventViewer(bool show)
1914 {
1915     if (show) {
1916         mEventViewerBox->show();
1917     } else {
1918         mEventViewerBox->hide();
1919     }
1920 }
1921 
addView(KOrg::BaseView * view)1922 void CalendarView::addView(KOrg::BaseView *view)
1923 {
1924     mViewManager->addView(view);
1925 }
1926 
showView(KOrg::BaseView * view)1927 void CalendarView::showView(KOrg::BaseView *view)
1928 {
1929     mViewManager->showView(view);
1930 }
1931 
addExtension(CalendarViewExtension::Factory * factory)1932 void CalendarView::addExtension(CalendarViewExtension::Factory *factory)
1933 {
1934     CalendarViewExtension *extension = factory->create(mLeftSplitter);
1935     mExtensions.append(extension);
1936     if (!mETMCollectionView) {
1937         mETMCollectionView = qobject_cast<AkonadiCollectionView *>(extension);
1938     }
1939 }
1940 
showLeftFrame(bool show)1941 void CalendarView::showLeftFrame(bool show)
1942 {
1943     if (show) {
1944         mMainSplitterSizes.clear();
1945         mLeftFrame->show();
1946         Q_EMIT calendarViewExpanded(false);
1947     } else {
1948         // mPanner splitter sizes are useless if mLeftFrame is hidden, so remember them beforehand.
1949         if (mMainSplitterSizes.isEmpty()) {
1950             mMainSplitterSizes = mPanner->sizes();
1951         }
1952 
1953         mLeftFrame->hide();
1954         Q_EMIT calendarViewExpanded(true);
1955     }
1956 }
1957 
selectedTodo()1958 Akonadi::Item CalendarView::selectedTodo()
1959 {
1960     const Akonadi::Item item = currentSelection();
1961     if (const KCalendarCore::Todo::Ptr t = CalendarSupport::todo(item)) {
1962         return item;
1963     }
1964 
1965     Akonadi::Item incidence;
1966 
1967     const Akonadi::Item::List selectedIncidences = mTodoList->selectedIncidences();
1968     if (!selectedIncidences.isEmpty()) {
1969         incidence = selectedIncidences.first();
1970     }
1971     if (const KCalendarCore::Todo::Ptr t = CalendarSupport::todo(item)) {
1972         return item;
1973     }
1974     return Akonadi::Item();
1975 }
1976 
dialogClosing(const Akonadi::Item &)1977 void CalendarView::dialogClosing(const Akonadi::Item &)
1978 {
1979 }
1980 
currentSelection()1981 Akonadi::Item CalendarView::currentSelection()
1982 {
1983     return mViewManager->currentSelection();
1984 }
1985 
selectedIncidence()1986 Akonadi::Item CalendarView::selectedIncidence()
1987 {
1988     Akonadi::Item item = currentSelection();
1989     if (!item.isValid()) {
1990         Akonadi::Item::List selectedIncidences = mTodoList->selectedIncidences();
1991         if (!selectedIncidences.isEmpty()) {
1992             item = selectedIncidences.first();
1993         }
1994     }
1995     return item;
1996 }
1997 
showIncidence()1998 void CalendarView::showIncidence()
1999 {
2000     showIncidence(selectedIncidence());
2001 }
2002 
editIncidence()2003 void CalendarView::editIncidence()
2004 {
2005     editIncidence(selectedIncidence());
2006 }
2007 
editIncidence(Akonadi::Item::Id id)2008 bool CalendarView::editIncidence(Akonadi::Item::Id id)
2009 {
2010     Akonadi::Item item = mCalendar->item(id);
2011     return editIncidence(item);
2012 }
2013 
showIncidence(Akonadi::Item::Id id)2014 bool CalendarView::showIncidence(Akonadi::Item::Id id)
2015 {
2016     Akonadi::Item item = mCalendar->item(id);
2017     if (!CalendarSupport::hasIncidence(item)) {
2018         return false;
2019     }
2020     showIncidence(item);
2021     return true;
2022 }
2023 
showIncidenceContext(Akonadi::Item::Id id)2024 bool CalendarView::showIncidenceContext(Akonadi::Item::Id id)
2025 {
2026     Akonadi::Item item = mCalendar->item(id);
2027     if (!CalendarSupport::hasIncidence(item)) {
2028         return false;
2029     }
2030     showIncidenceContext(item);
2031     return true;
2032 }
2033 
deleteIncidence()2034 void CalendarView::deleteIncidence()
2035 {
2036     deleteIncidence(selectedIncidence());
2037 }
2038 
cutIncidence(const Akonadi::Item & incidence)2039 void CalendarView::cutIncidence(const Akonadi::Item &incidence)
2040 {
2041     Q_UNUSED(incidence)
2042     edit_cut();
2043 }
2044 
copyIncidence(const Akonadi::Item & incidence)2045 void CalendarView::copyIncidence(const Akonadi::Item &incidence)
2046 {
2047     Q_UNUSED(incidence)
2048     edit_copy();
2049 }
2050 
pasteIncidence()2051 void CalendarView::pasteIncidence()
2052 {
2053     edit_paste();
2054 }
2055 
showIncidence(const Akonadi::Item & item)2056 void CalendarView::showIncidence(const Akonadi::Item &item)
2057 {
2058     auto eventViewer = new KOEventViewerDialog(mCalendar.data(), this);
2059     eventViewer->setIncidence(item, QDate());
2060     // Disable the Edit button for read-only Incidences.
2061     if (!mCalendar->hasRight(item, Akonadi::Collection::CanChangeItem)) {
2062         eventViewer->editButton()->setEnabled(false);
2063     }
2064 
2065     eventViewer->show();
2066 }
2067 
showIncidenceContext(const Akonadi::Item & item)2068 void CalendarView::showIncidenceContext(const Akonadi::Item &item)
2069 {
2070     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
2071     if (CalendarSupport::hasEvent(item)) {
2072         if (!viewManager()->currentView()->inherits("KOEventView")) {
2073             viewManager()->showAgendaView();
2074         }
2075         // just select the appropriate date
2076         mDateNavigator->selectWeek(incidence->dtStart().toLocalTime().date());
2077         return;
2078     } else if (CalendarSupport::hasJournal(item)) {
2079         if (!viewManager()->currentView()->inherits("KOJournalView")) {
2080             viewManager()->showJournalView();
2081         }
2082     } else if (CalendarSupport::hasTodo(item)) {
2083         if (!viewManager()->currentView()->inherits("KOTodoView")) {
2084             viewManager()->showTodoView();
2085         }
2086     }
2087     Akonadi::Item::List list;
2088     list.append(item);
2089     viewManager()->currentView()->showIncidences(list, QDate());
2090 }
2091 
editIncidence(const Akonadi::Item & item,bool isCounter)2092 bool CalendarView::editIncidence(const Akonadi::Item &item, bool isCounter)
2093 {
2094     Q_UNUSED(isCounter)
2095     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
2096     if (!incidence) {
2097         qCCritical(KORGANIZER_LOG) << "Null incidence";
2098         KNotification::beep();
2099         return false;
2100     }
2101 
2102     if (!mCalendar->hasRight(item, Akonadi::Collection::CanChangeItem)) {
2103         showIncidence(item);
2104         return true;
2105     }
2106 
2107     IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog(item);
2108     dialog->load(item, activeIncidenceDate()); // Show the dialog as soon as it loads the item.
2109 
2110     return true;
2111 }
2112 
deleteIncidenceFamily(const Akonadi::Item & item)2113 void CalendarView::deleteIncidenceFamily(const Akonadi::Item &item)
2114 {
2115     const auto incidence = CalendarSupport::incidence(item);
2116     if (!incidence) {
2117         return;
2118     }
2119     deleteChildren(item);
2120     deleteRecurringIncidence(item);
2121 }
2122 
deleteChildren(const Akonadi::Item & item)2123 void CalendarView::deleteChildren(const Akonadi::Item &item)
2124 {
2125     const auto incidence = CalendarSupport::incidence(item);
2126     if (incidence && !incidence->hasRecurrenceId()) {
2127         const Akonadi::Item::List childItems = mCalendar->childItems(item.id());
2128         for (const Akonadi::Item &c : childItems) {
2129             deleteIncidenceFamily(c);
2130         }
2131     }
2132 }
2133 
deleteRecurringIncidence(const Akonadi::Item & todoItem)2134 void CalendarView::deleteRecurringIncidence(const Akonadi::Item &todoItem)
2135 {
2136     if (!mChanger->deletedRecently(todoItem.id())) {
2137         auto incidence = CalendarSupport::incidence(todoItem);
2138         if (incidence->recurs()) {
2139             for (const auto &instance : mCalendar->instances(incidence)) {
2140                 (void) mChanger->deleteIncidence(mCalendar->item(instance), this);
2141             }
2142         }
2143         (void) mChanger->deleteIncidence(todoItem, this);
2144     }
2145 }
2146 
questionIndependentChildren(const Akonadi::Item & item)2147 int CalendarView::questionIndependentChildren(const Akonadi::Item &item)
2148 {
2149     int km;
2150     auto incidence = CalendarSupport::incidence(item);
2151     if (!incidence->hasRecurrenceId() && !mCalendar->childItems(item.id()).isEmpty()) {
2152         km = KMessageBox::questionYesNoCancel(this,
2153                                                 i18n("The item \"%1\" has sub-to-dos. "
2154                                                     "Do you want to delete just this item and "
2155                                                     "make all its sub-to-dos independent, or "
2156                                                     "delete the to-do with all its sub-to-dos?",
2157                                                     incidence->summary()),
2158                                                 i18n("KOrganizer Confirmation"),
2159                                                 KGuiItem(i18n("Delete Only This")),
2160                                                 KGuiItem(i18n("Delete All")));
2161 
2162         if (km == KMessageBox::No) {
2163             km = KMessageBox::Continue;
2164         }
2165     } else {
2166         km = msgItemDelete(item);
2167     }
2168     return km;
2169 }
2170 
deleteIncidence(const Akonadi::Item & item,bool force)2171 bool CalendarView::deleteIncidence(const Akonadi::Item &item, bool force)
2172 {
2173     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
2174     if (!incidence) {
2175         if (!force) {
2176             qCCritical(KORGANIZER_LOG) << "Null incidence";
2177             KNotification::beep();
2178         }
2179         qCCritical(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): Unable to delete, incidence is null.";
2180         return false;
2181     }
2182 
2183     if (mChanger->deletedRecently(item.id())) {
2184         // it was deleted already but the etm wasn't notified yet
2185         qCWarning(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): item with id" << item.id() << "was deleted recently, skipping";
2186         return true;
2187     }
2188 
2189     if (!mCalendar->hasRight(item, Akonadi::Collection::CanDeleteItem)) {
2190         if (!force) {
2191             KMessageBox::information(this,
2192                                      i18n("The item \"%1\" is marked read-only "
2193                                           "and cannot be deleted; it probably "
2194                                           "belongs to a read-only calendar.",
2195                                           incidence->summary()),
2196                                      i18n("Removing not possible"),
2197                                      QStringLiteral("deleteReadOnlyIncidence"));
2198         }
2199         qCWarning(KORGANIZER_LOG) << "CalendarView::deleteIncidence(): No rights to delete item";
2200         return false;
2201     }
2202 
2203     QDate itemDate = mViewManager->currentSelectionDate();
2204 
2205     int km;
2206     if (force) {
2207         km = ItemActions::All;
2208     } else if (!incidence->recurs()) {  // Non-recurring, or instance of recurring.
2209         km = questionIndependentChildren(item);
2210     } else { // Recurring incidence
2211         if (!itemDate.isValid()) {
2212             qCDebug(KORGANIZER_LOG) << "Date Not Valid";
2213             km = KMessageBox::warningContinueCancel(this,
2214                                                     i18n("The calendar item \"%1\" recurs over multiple dates; "
2215                                                             "are you sure you want to delete it "
2216                                                             "and all its recurrences?",
2217                                                             incidence->summary()),
2218                                                     i18n("KOrganizer Confirmation"),
2219                                                     KGuiItem(i18n("Delete All")));
2220         } else {
2221             QDateTime itemDateTime(itemDate, {}, Qt::LocalTime);
2222             bool isFirst = !incidence->recurrence()->getPreviousDateTime(itemDateTime).isValid();
2223             bool isLast = !incidence->recurrence()->getNextDateTime(itemDateTime).isValid();
2224 
2225             QString message;
2226             QString itemFuture(i18n("Also Delete &Future")); // QT5 was a KGuiItem
2227 
2228             if (!isFirst && !isLast) {
2229                 message = i18n(
2230                     "The calendar item \"%1\" recurs over multiple dates. "
2231                     "Do you want to delete only the current one on %2, also "
2232                     "future occurrences, or all its occurrences?",
2233                     incidence->summary(),
2234                     QLocale::system().toString(itemDate, QLocale::LongFormat));
2235             } else {
2236                 message = i18n(
2237                     "The calendar item \"%1\" recurs over multiple dates. "
2238                     "Do you want to delete only the current one on %2 "
2239                     "or all its occurrences?",
2240                     incidence->summary(),
2241                     QLocale::system().toString(itemDate, QLocale::LongFormat));
2242             }
2243 
2244             if (!(isFirst && isLast)) {
2245                 QDialogButtonBox::StandardButton returnValue = PIMMessageBox::fourBtnMsgBox(this,
2246                                                                                             QMessageBox::Warning,
2247                                                                                             message,
2248                                                                                             i18n("KOrganizer Confirmation"),
2249                                                                                             i18n("Delete C&urrent"),
2250                                                                                             itemFuture,
2251                                                                                             i18n("Delete &All"));
2252                 switch (returnValue) {
2253                 case QDialogButtonBox::Ok:
2254                     if (!mCalendar->childItems(item.id()).isEmpty()) {
2255                         km = questionIndependentChildren(item);
2256                     } else {
2257                         km = ItemActions::All;
2258                     }
2259                     break;
2260                 case QDialogButtonBox::Yes:
2261                     km = ItemActions::Current;
2262                     break;
2263                 case QDialogButtonBox::No:
2264                     km = ItemActions::AlsoFuture;
2265                     break;
2266                 case QDialogButtonBox::Cancel:
2267                 default:
2268                     km = KMessageBox::Cancel;
2269                     break;
2270                 }
2271             } else {
2272                 km = questionIndependentChildren(item);
2273             }
2274         }
2275     }
2276 
2277     KCalendarCore::Incidence::Ptr oldIncidence(incidence->clone());
2278     KCalendarCore::Recurrence *recur = incidence->recurrence();
2279 
2280     switch (km) {
2281     case ItemActions::All:
2282         startMultiModify(i18n("Delete \"%1\"", incidence->summary()));
2283         deleteChildren(item);
2284         deleteRecurringIncidence(item);
2285         endMultiModify();
2286         break;
2287 
2288     case ItemActions::Parent:
2289         startMultiModify(i18n("Delete \"%1\"", incidence->summary()));
2290         makeChildrenIndependent(item);
2291         deleteRecurringIncidence(item);
2292         endMultiModify();
2293         break;
2294 
2295     case ItemActions::Current:
2296         if (recur->allDay()) {
2297             recur->addExDate(itemDate);
2298         } else {
2299             auto itemDateTime = recur->startDateTime();
2300             itemDateTime.setDate(itemDate);
2301             recur->addExDateTime(itemDateTime);
2302         }
2303         (void) mChanger->modifyIncidence(item, oldIncidence, this);
2304         break;
2305 
2306     case ItemActions::AlsoFuture:
2307         recur->setEndDate(itemDate.addDays(-1));
2308         (void) mChanger->modifyIncidence(item, oldIncidence, this);
2309         break;
2310     }
2311     return true;
2312 }
2313 
purgeCompleted()2314 void CalendarView::purgeCompleted()
2315 {
2316     if (checkedCollections().isEmpty()) {
2317         showMessage(i18n("All calendars are unchecked in the Calendar Manager. No to-do was purged."), KMessageWidget::Warning);
2318         return;
2319     }
2320 
2321     if (mCalendar->rawTodos().isEmpty()) {
2322         showMessage(i18n("There are no completed to-dos to purge."), KMessageWidget::Information);
2323         return;
2324     }
2325 
2326     int result = KMessageBox::warningContinueCancel(this,
2327                                                     i18n("Delete all completed to-dos from checked calendars?"),
2328                                                     i18n("Purge To-dos"),
2329                                                     KGuiItem(i18n("Purge"), QIcon::fromTheme(QStringLiteral("entry-delete"))));
2330 
2331     if (result == KMessageBox::Continue) {
2332         mTodoPurger->purgeCompletedTodos();
2333     }
2334 }
2335 
warningChangeFailed(const Akonadi::Item & item)2336 void CalendarView::warningChangeFailed(const Akonadi::Item &item)
2337 {
2338     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
2339     if (incidence) {
2340         KMessageBox::sorry(this, i18nc("@info", "Unable to edit \"%1\" because it is locked by another process.", incidence->summary()));
2341     }
2342 }
2343 
showErrorMessage(const QString & msg)2344 void CalendarView::showErrorMessage(const QString &msg)
2345 {
2346     KMessageBox::error(this, msg);
2347 }
2348 
addIncidenceOn(const Akonadi::Item & itemadd,const QDate & dt)2349 void CalendarView::addIncidenceOn(const Akonadi::Item &itemadd, const QDate &dt)
2350 {
2351     if (!CalendarSupport::hasIncidence(itemadd)) {
2352         KMessageBox::sorry(this, i18n("Unable to copy the item to %1.", dt.toString()), i18n("Copying Failed"));
2353         return;
2354     }
2355     Akonadi::Item item = mCalendar->item(itemadd.id());
2356     if (!item.isValid()) {
2357         item = itemadd;
2358     }
2359     // Create a copy of the incidence, since the incadd doesn't belong to us.
2360     KCalendarCore::Incidence::Ptr incidence(CalendarSupport::incidence(item)->clone());
2361     incidence->recreate();
2362 
2363     if (const KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>()) {
2364         // Adjust date
2365         QDateTime start = event->dtStart();
2366         QDateTime end = event->dtEnd();
2367 
2368         int duration = start.daysTo(end);
2369         start.setDate(dt);
2370         end.setDate(dt.addDays(duration));
2371 
2372         event->setDtStart(start);
2373         event->setDtEnd(end);
2374     } else if (const KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>()) {
2375         QDateTime due = todo->dtDue();
2376         due.setDate(dt);
2377 
2378         todo->setDtDue(due);
2379     }
2380 
2381     (void) mChanger->createIncidence(incidence, Akonadi::Collection(), this);
2382 }
2383 
moveIncidenceTo(const Akonadi::Item & itemmove,QDate dt)2384 void CalendarView::moveIncidenceTo(const Akonadi::Item &itemmove, QDate dt)
2385 {
2386     if (!CalendarSupport::hasIncidence(itemmove)) {
2387         KMessageBox::sorry(this, i18n("Unable to move the item to  %1.", dt.toString()), i18n("Moving Failed"));
2388         return;
2389     }
2390     Akonadi::Item item = mCalendar->item(itemmove.id());
2391     if (!item.isValid()) {
2392         addIncidenceOn(itemmove, dt);
2393         return;
2394     }
2395     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(itemmove);
2396 
2397     KCalendarCore::Incidence::Ptr oldIncidence(incidence->clone());
2398 
2399     if (const KCalendarCore::Event::Ptr event = incidence.dynamicCast<KCalendarCore::Event>()) {
2400         // Adjust date
2401         QDateTime start = event->dtStart();
2402         QDateTime end = event->dtEnd();
2403 
2404         int duration = start.daysTo(end);
2405         start.setDate(dt);
2406         end.setDate(dt.addDays(duration));
2407 
2408         event->setDtStart(start);
2409         event->setDtEnd(end);
2410     }
2411     if (const KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>()) {
2412         QDateTime due = todo->dtDue();
2413         due.setDate(dt);
2414 
2415         todo->setDtDue(due);
2416     }
2417     (void) mChanger->modifyIncidence(itemmove, oldIncidence, this);
2418 }
2419 
resourcesChanged()2420 void CalendarView::resourcesChanged()
2421 {
2422     mViewManager->addChange(EventViews::EventView::ResourcesChanged);
2423     updateView();
2424 }
2425 
eventFilter(QObject * watched,QEvent * event)2426 bool CalendarView::eventFilter(QObject *watched, QEvent *event)
2427 {
2428     if (watched == mLeftFrame && event->type() == QEvent::Show) {
2429         mSplitterSizesValid = true;
2430     }
2431     return KOrg::CalendarViewBase::eventFilter(watched, event);
2432 }
2433 
updateHighlightModes()2434 void CalendarView::updateHighlightModes()
2435 {
2436     KOrg::BaseView *view = mViewManager->currentView();
2437     if (view) {
2438         bool hiEvents;
2439         bool hiTodos;
2440         bool hiJournals;
2441 
2442         view->getHighlightMode(hiEvents, hiTodos, hiJournals);
2443         mDateNavigatorContainer->setHighlightMode(hiEvents, hiTodos, hiJournals);
2444     }
2445 }
2446 
selectWeek(const QDate & date,const QDate & preferredMonth)2447 void CalendarView::selectWeek(const QDate &date, const QDate &preferredMonth)
2448 {
2449     if (KOPrefs::instance()->mWeekNumbersShowWork && mViewManager->rangeMode() == KOViewManager::WORK_WEEK_RANGE) {
2450         mDateNavigator->selectWorkWeek(date);
2451     } else {
2452         mDateNavigator->selectWeek(date, preferredMonth);
2453     }
2454 }
2455 
changeFullView(bool fullView)2456 void CalendarView::changeFullView(bool fullView)
2457 {
2458     if (mViewManager->currentView()) {
2459         if (mViewManager->currentView()->identifier() == "DefaultTodoView") {
2460             showLeftFrame(!fullView);
2461         } else if (mViewManager->currentView()->identifier() == "DefaultMonthView") {
2462             showLeftFrame(!fullView);
2463             fullView ? mNavigatorBar->show() : mNavigatorBar->hide();
2464         }
2465     }
2466 }
2467 
defaultCollection(const QLatin1String & mimeType) const2468 Akonadi::Collection CalendarView::defaultCollection(const QLatin1String &mimeType) const
2469 {
2470     // 1. Try the view collection ( used in multi-agenda view )
2471     Akonadi::Collection collection = mCalendar->collection(mViewManager->currentView()->collectionId());
2472     bool supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
2473     bool hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
2474     if (collection.isValid() && supportsMimeType && hasRights) {
2475         return collection;
2476     }
2477 
2478     // 2. Try the configured default collection
2479     collection = mCalendar->collection(CalendarSupport::KCalPrefs::instance()->defaultCalendarId());
2480     supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
2481     hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
2482     if (collection.isValid() && supportsMimeType && hasRights) {
2483         return collection;
2484     }
2485 
2486     // 3. Try the selected collection
2487     collection = selectedCollection();
2488     supportsMimeType = collection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
2489     hasRights = collection.rights() & Akonadi::Collection::CanCreateItem;
2490     if (collection.isValid() && supportsMimeType && hasRights) {
2491         return collection;
2492     }
2493 
2494     // 4. Try the checked collections
2495     const Akonadi::Collection::List collections = checkedCollections();
2496     for (const Akonadi::Collection &checkedCollection : collections) {
2497         supportsMimeType = checkedCollection.contentMimeTypes().contains(mimeType) || mimeType == QLatin1String("");
2498         hasRights = checkedCollection.rights() & Akonadi::Collection::CanCreateItem;
2499         if (checkedCollection.isValid() && supportsMimeType && hasRights) {
2500             return checkedCollection;
2501         }
2502     }
2503 
2504     // 5. Return a invalid collection, the editor will use the first one in the combo
2505     return Akonadi::Collection();
2506 }
2507 
createIncidenceEditor(const Akonadi::Item & item,const Akonadi::Collection & collection)2508 IncidenceEditorNG::IncidenceDialog *CalendarView::createIncidenceEditor(const Akonadi::Item &item, const Akonadi::Collection &collection)
2509 {
2510     IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog(item);
2511     KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(item);
2512     Q_ASSERT(incidence);
2513 
2514     if (collection.isValid()) {
2515         dialog->selectCollection(collection);
2516     } else {
2517         dialog->selectCollection(defaultCollection(incidence->mimeType()));
2518     }
2519 
2520     return dialog;
2521 }
2522 
history() const2523 Akonadi::History *CalendarView::history() const
2524 {
2525     return mChanger->history();
2526 }
2527 
onCutFinished()2528 void CalendarView::onCutFinished()
2529 {
2530     checkClipboard();
2531 }
2532 
onCheckableProxyAboutToToggle(bool newState)2533 void CalendarView::onCheckableProxyAboutToToggle(bool newState)
2534 {
2535     // Someone unchecked a collection, save the view state now.
2536     if (!newState) {
2537         mTodoList->saveViewState();
2538         KOTodoView *todoView = mViewManager->todoView();
2539         if (todoView) {
2540             todoView->saveViewState();
2541         }
2542     }
2543 }
2544 
onCheckableProxyToggled(bool newState)2545 void CalendarView::onCheckableProxyToggled(bool newState)
2546 {
2547     // Someone checked a collection, restore the view state now.
2548     if (newState) {
2549         mTodoList->restoreViewState();
2550         KOTodoView *todoView = mViewManager->todoView();
2551         if (todoView) {
2552             todoView->restoreViewState();
2553         }
2554     }
2555 }
2556 
onTodosPurged(bool success,int numDeleted,int numIgnored)2557 void CalendarView::onTodosPurged(bool success, int numDeleted, int numIgnored)
2558 {
2559     QString message;
2560     KMessageWidget::MessageType type = KMessageWidget::Information;
2561     if (success) {
2562         if (numDeleted == 0 && numIgnored > 0) {
2563             type = KMessageWidget::Warning;
2564             message = i18n("0 completed to-dos were purged.") + QLatin1Char('\n')
2565                 + i18np("%1 to-do was ignored because it has uncompleted or read-only children.",
2566                         "%1 to-dos were ignored because they have uncompleted or read-only children.",
2567                         numIgnored);
2568         } else if (numDeleted > 0 && numIgnored == 0) {
2569             message = i18np("%1 completed to-do was purged.", "%1 completed to-dos were purged.", numDeleted);
2570         } else if (numDeleted == 0 && numIgnored == 0) {
2571             message = i18n("There are no completed to-dos to purge.");
2572         } else {
2573             type = KMessageWidget::Warning;
2574             message = i18np("%1 completed to-do was purged.", "%1 completed to-dos were purged.", numDeleted) + QLatin1Char('\n')
2575                 + i18np("%1 to-do was ignored because it has uncompleted or read-only children.",
2576                         "%1 to-dos were ignored because they have uncompleted or read-only children.",
2577                         numIgnored);
2578         }
2579     } else {
2580         message = i18n("An error occurred while purging completed to-dos: %1", mTodoPurger->lastError());
2581         type = KMessageWidget::Error;
2582     }
2583 
2584     showMessage(message, type);
2585 }
2586 
showMessage(const QString & message,KMessageWidget::MessageType type)2587 void CalendarView::showMessage(const QString &message, KMessageWidget::MessageType type)
2588 {
2589     mMessageWidget->setText(message);
2590     mMessageWidget->setMessageType(type);
2591     mMessageWidget->show();
2592 }
2593 
selectedCollection() const2594 Akonadi::Collection CalendarView::selectedCollection() const
2595 {
2596     return mETMCollectionView ? mETMCollectionView->selectedCollection() : Akonadi::Collection();
2597 }
2598 
checkedCollections() const2599 Akonadi::Collection::List CalendarView::checkedCollections() const
2600 {
2601     Akonadi::Collection::List collections;
2602     if (mETMCollectionView) {
2603         collections = mETMCollectionView->checkedCollections();
2604     }
2605 
2606     // If the default calendar is here, it should be first.
2607     int count = collections.count();
2608     Akonadi::Collection::Id id = CalendarSupport::KCalPrefs::instance()->defaultCalendarId();
2609     for (int i = 0; i < count; ++i) {
2610         if (id == collections[i].id()) {
2611             const Akonadi::Collection col = collections.takeAt(i);
2612             collections.insert(0, col);
2613             break;
2614         }
2615     }
2616 
2617     return collections;
2618 }
2619 
handleIncidenceCreated(const Akonadi::Item & item)2620 void CalendarView::handleIncidenceCreated(const Akonadi::Item &item)
2621 {
2622     Akonadi::Collection collection = item.parentCollection();
2623     if (!collection.isValid()) {
2624         qCWarning(KORGANIZER_LOG) << "Item was creating in an invalid collection !? item id=" << item.id();
2625         return;
2626     }
2627 
2628     const bool collectionIsChecked = mETMCollectionView->isChecked(collection);
2629 
2630     if (!collectionIsChecked) {
2631         QString message;
2632         if (mETMCollectionView->isVisible()) {
2633             message = i18n(
2634                 "You created an incidence in a calendar that is currently filtered out.\n"
2635                 "On the left sidebar, enable it in the calendar manager to see the incidence.");
2636         } else {
2637             message = i18n(
2638                 "You created an incidence in a calendar that is currently filtered out.\n"
2639                 "You can enable it through the calendar manager (Settings->Sidebar->Show Calendar Manager)");
2640         }
2641 
2642         mMessageWidget->setText(message);
2643         mMessageWidget->setMessageType(KMessageWidget::Information);
2644         mMessageWidget->show();
2645     }
2646 }
2647