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