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