File indexing completed on 2024-04-28 05:11:35
0001 /* 0002 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org> 0003 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0004 SPDX-FileCopyrightText: 2012 Allen Winter <winter@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "incidencedialog.h" 0010 #include "combinedincidenceeditor.h" 0011 #include "editorconfig.h" 0012 #include "incidencealarm.h" 0013 #include "incidenceattachment.h" 0014 #include "incidenceattendee.h" 0015 #include "incidencecategories.h" 0016 #include "incidencecompletionpriority.h" 0017 #include "incidencedatetime.h" 0018 #include "incidencedescription.h" 0019 #include "incidenceeditor_debug.h" 0020 #include "incidencerecurrence.h" 0021 #include "incidenceresource.h" 0022 #include "incidencesecrecy.h" 0023 #include "incidencewhatwhere.h" 0024 #include "templatemanagementdialog.h" 0025 #include "ui_dialogdesktop.h" 0026 0027 #include "incidenceeditorsettings.h" 0028 0029 #include <CalendarSupport/KCalPrefs> 0030 #include <CalendarSupport/Utils> 0031 0032 #include <Akonadi/CalendarUtils> 0033 #include <Akonadi/CollectionComboBox> 0034 #include <Akonadi/ETMCalendar> 0035 #include <Akonadi/EntityTreeModel> 0036 #include <Akonadi/Item> 0037 0038 #include <KCalUtils/Stringify> 0039 #include <KCalendarCore/ICalFormat> 0040 #include <KCalendarCore/MemoryCalendar> 0041 0042 #include <KMessageBox> 0043 #include <KSharedConfig> 0044 0045 #include <KWindowConfig> 0046 #include <QCloseEvent> 0047 #include <QDir> 0048 #include <QIcon> 0049 #include <QStandardPaths> 0050 #include <QTimeZone> 0051 #include <QWindow> 0052 0053 using namespace IncidenceEditorNG; 0054 namespace 0055 { 0056 static const char myIncidenceDialogConfigGroupName[] = "IncidenceDialog"; 0057 0058 IncidenceEditorNG::EditorItemManager::ItipPrivacyFlags toItemManagerFlags(bool sign, bool encrypt) 0059 { 0060 IncidenceEditorNG::EditorItemManager::ItipPrivacyFlags flags; 0061 flags.setFlag(IncidenceEditorNG::EditorItemManager::ItipPrivacySign, sign); 0062 flags.setFlag(IncidenceEditorNG::EditorItemManager::ItipPrivacyEncrypt, encrypt); 0063 return flags; 0064 } 0065 } 0066 namespace IncidenceEditorNG 0067 { 0068 enum Tabs { GeneralTab = 0, AttendeesTab, ResourcesTab, AlarmsTab, RecurrenceTab, AttachmentsTab }; 0069 0070 class IncidenceDialogPrivate : public ItemEditorUi 0071 { 0072 IncidenceDialog *q_ptr; 0073 Q_DECLARE_PUBLIC(IncidenceDialog) 0074 0075 public: 0076 Ui::EventOrTodoDesktop *mUi = nullptr; 0077 Akonadi::CollectionComboBox *mCalSelector = nullptr; 0078 bool mCloseOnSave = false; 0079 0080 EditorItemManager *mItemManager = nullptr; 0081 CombinedIncidenceEditor *mEditor = nullptr; 0082 IncidenceDateTime *mIeDateTime = nullptr; 0083 IncidenceAttendee *mIeAttendee = nullptr; 0084 IncidenceRecurrence *mIeRecurrence = nullptr; 0085 IncidenceResource *mIeResource = nullptr; 0086 bool mInitiallyDirty = false; 0087 Akonadi::Item mItem; 0088 [[nodiscard]] QString typeToString(const int type) const; 0089 0090 public: 0091 IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq); 0092 ~IncidenceDialogPrivate() override; 0093 0094 /// General methods 0095 void handleAlarmCountChange(int newCount); 0096 void handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type); 0097 void loadTemplate(const QString &templateName); 0098 void manageTemplates(); 0099 void saveTemplate(const QString &templateName); 0100 void storeTemplatesInConfig(const QStringList &newTemplates); 0101 void updateAttachmentCount(int newCount); 0102 void updateAttendeeCount(int newCount); 0103 void updateResourceCount(int newCount); 0104 void updateButtonStatus(bool isDirty); 0105 void showMessage(const QString &text, KMessageWidget::MessageType type); 0106 void slotInvalidCollection(); 0107 void setCalendarCollection(const Akonadi::Collection &collection); 0108 0109 /// ItemEditorUi methods 0110 [[nodiscard]] bool containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const override; 0111 void handleItemSaveFinish(EditorItemManager::SaveAction); 0112 void handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage); 0113 [[nodiscard]] bool hasSupportedPayload(const Akonadi::Item &item) const override; 0114 [[nodiscard]] bool isDirty() const override; 0115 [[nodiscard]] bool isValid() const override; 0116 void load(const Akonadi::Item &item) override; 0117 Akonadi::Item save(const Akonadi::Item &item) override; 0118 [[nodiscard]] Akonadi::Collection selectedCollection() const override; 0119 0120 void reject(RejectReason reason, const QString &errorMessage = QString()) override; 0121 }; 0122 } 0123 0124 IncidenceDialogPrivate::IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq) 0125 : q_ptr(qq) 0126 , mUi(new Ui::EventOrTodoDesktop) 0127 , mCalSelector(new Akonadi::CollectionComboBox(changer ? changer->entityTreeModel() : nullptr)) 0128 , mItemManager(new EditorItemManager(this, changer)) 0129 , mEditor(new CombinedIncidenceEditor(qq)) 0130 { 0131 Q_Q(IncidenceDialog); 0132 mUi->setupUi(q); 0133 mUi->mMessageWidget->hide(); 0134 auto layout = new QGridLayout(mUi->mCalSelectorPlaceHolder); 0135 layout->setSpacing(0); 0136 layout->setContentsMargins(0, 0, 0, 0); 0137 layout->addWidget(mCalSelector); 0138 mCalSelector->setAccessRightsFilter(Akonadi::Collection::CanCreateItem); 0139 mUi->label->setBuddy(mCalSelector); 0140 q->connect(mCalSelector, &Akonadi::CollectionComboBox::currentChanged, q, &IncidenceDialog::handleSelectedCollectionChange); 0141 0142 // Now instantiate the logic of the dialog. These editors update the ui, validate 0143 // fields and load/store incidences in the ui. 0144 auto ieGeneral = new IncidenceWhatWhere(mUi); 0145 mEditor->combine(ieGeneral); 0146 0147 auto ieCategories = new IncidenceCategories(mUi); 0148 mEditor->combine(ieCategories); 0149 0150 mIeDateTime = new IncidenceDateTime(mUi); 0151 mEditor->combine(mIeDateTime); 0152 0153 auto ieCompletionPriority = new IncidenceCompletionPriority(mUi); 0154 mEditor->combine(ieCompletionPriority); 0155 0156 auto ieDescription = new IncidenceDescription(mUi); 0157 mEditor->combine(ieDescription); 0158 0159 auto ieAlarm = new IncidenceAlarm(mIeDateTime, mUi); 0160 mEditor->combine(ieAlarm); 0161 0162 auto ieAttachments = new IncidenceAttachment(mUi); 0163 mEditor->combine(ieAttachments); 0164 0165 mIeRecurrence = new IncidenceRecurrence(mIeDateTime, mUi); 0166 mEditor->combine(mIeRecurrence); 0167 0168 auto ieSecrecy = new IncidenceSecrecy(mUi); 0169 mEditor->combine(ieSecrecy); 0170 0171 mIeAttendee = new IncidenceAttendee(qq, mIeDateTime, mUi); 0172 mIeAttendee->setParent(qq); 0173 mEditor->combine(mIeAttendee); 0174 0175 mIeResource = new IncidenceResource(mIeAttendee, mIeDateTime, mUi); 0176 mEditor->combine(mIeResource); 0177 0178 // Set the default collection 0179 const qint64 colId = CalendarSupport::KCalPrefs::instance()->defaultCalendarId(); 0180 const Akonadi::Collection col(colId); 0181 setCalendarCollection(col); 0182 0183 q->connect(mEditor, &CombinedIncidenceEditor::showMessage, q, [this](const QString &reason, KMessageWidget::MessageType msgType) { 0184 showMessage(reason, msgType); 0185 }); 0186 q->connect(mEditor, &IncidenceEditor::dirtyStatusChanged, q, [this](bool isDirty) { 0187 updateButtonStatus(isDirty); 0188 }); 0189 q->connect(mItemManager, &EditorItemManager::itemSaveFinished, q, [this](EditorItemManager::SaveAction action) { 0190 handleItemSaveFinish(action); 0191 }); 0192 q->connect(mItemManager, &EditorItemManager::itemSaveFailed, q, [this](EditorItemManager::SaveAction action, const QString &message) { 0193 handleItemSaveFail(action, message); 0194 }); 0195 q->connect(ieAlarm, &IncidenceAlarm::alarmCountChanged, q, [this](int newCount) { 0196 handleAlarmCountChange(newCount); 0197 }); 0198 q->connect(mIeRecurrence, &IncidenceRecurrence::recurrenceChanged, q, [this](IncidenceEditorNG::RecurrenceType type) { 0199 handleRecurrenceChange(type); 0200 }); 0201 q->connect(ieAttachments, &IncidenceAttachment::attachmentCountChanged, q, [this](int newCount) { 0202 updateAttachmentCount(newCount); 0203 }); 0204 q->connect(mIeAttendee, &IncidenceAttendee::attendeeCountChanged, q, [this](int count) { 0205 updateAttendeeCount(count); 0206 }); 0207 q->connect(mIeResource, &IncidenceResource::resourceCountChanged, q, [this](int count) { 0208 updateResourceCount(count); 0209 }); 0210 } 0211 0212 IncidenceDialogPrivate::~IncidenceDialogPrivate() 0213 { 0214 delete mItemManager; 0215 delete mEditor; 0216 delete mUi; 0217 } 0218 0219 void IncidenceDialogPrivate::slotInvalidCollection() 0220 { 0221 showMessage(i18n("Select a valid collection first."), KMessageWidget::Warning); 0222 } 0223 0224 void IncidenceDialogPrivate::setCalendarCollection(const Akonadi::Collection &collection) 0225 { 0226 if (collection.isValid()) { 0227 mCalSelector->setDefaultCollection(collection); 0228 } else { 0229 mCalSelector->setCurrentIndex(0); 0230 } 0231 } 0232 0233 void IncidenceDialogPrivate::showMessage(const QString &text, KMessageWidget::MessageType type) 0234 { 0235 mUi->mMessageWidget->setText(text); 0236 mUi->mMessageWidget->setMessageType(type); 0237 mUi->mMessageWidget->show(); 0238 } 0239 0240 void IncidenceDialogPrivate::handleAlarmCountChange(int newCount) 0241 { 0242 QString tabText; 0243 if (newCount > 0) { 0244 tabText = i18nc("@title:tab Tab to configure the reminders of an event or todo", "Reminder (%1)", newCount); 0245 } else { 0246 tabText = i18nc("@title:tab Tab to configure the reminders of an event or todo", "Reminder"); 0247 } 0248 0249 mUi->mTabWidget->setTabText(AlarmsTab, tabText); 0250 } 0251 0252 void IncidenceDialogPrivate::handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type) 0253 { 0254 QString tabText = i18nc("@title:tab Tab to configure the recurrence of an event or todo", "Rec&urrence"); 0255 0256 // Keep this numbers in sync with the items in mUi->mRecurrenceTypeCombo. I 0257 // tried adding an enum to IncidenceRecurrence but for whatever reason I could 0258 // Qt not play nice with namespaced enums in signal/slot connections. 0259 // Anyways, I don't expect these values to change. 0260 switch (type) { 0261 case RecurrenceTypeNone: 0262 break; 0263 case RecurrenceTypeDaily: 0264 tabText += i18nc("@title:tab Daily recurring event, capital first letter only", " (D)"); 0265 break; 0266 case RecurrenceTypeWeekly: 0267 tabText += i18nc("@title:tab Weekly recurring event, capital first letter only", " (W)"); 0268 break; 0269 case RecurrenceTypeMonthly: 0270 tabText += i18nc("@title:tab Monthly recurring event, capital first letter only", " (M)"); 0271 break; 0272 case RecurrenceTypeYearly: 0273 tabText += i18nc("@title:tab Yearly recurring event, capital first letter only", " (Y)"); 0274 break; 0275 case RecurrenceTypeException: 0276 tabText += i18nc("@title:tab Exception to a recurring event, capital first letter only", " (E)"); 0277 break; 0278 default: 0279 Q_ASSERT_X(false, "handleRecurrenceChange", "Fix your program"); 0280 } 0281 0282 mUi->mTabWidget->setTabText(RecurrenceTab, tabText); 0283 } 0284 0285 QString IncidenceDialogPrivate::typeToString(const int type) const 0286 { 0287 // Do not translate. 0288 switch (type) { 0289 case KCalendarCore::Incidence::TypeEvent: 0290 return QStringLiteral("Event"); 0291 case KCalendarCore::Incidence::TypeTodo: 0292 return QStringLiteral("Todo"); 0293 case KCalendarCore::Incidence::TypeJournal: 0294 return QStringLiteral("Journal"); 0295 default: 0296 return QStringLiteral("Unknown"); 0297 } 0298 } 0299 0300 void IncidenceDialogPrivate::loadTemplate(const QString &templateName) 0301 { 0302 Q_Q(IncidenceDialog); 0303 0304 KCalendarCore::MemoryCalendar::Ptr cal(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone())); 0305 0306 const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0307 QStringLiteral("/korganizer/templates/") + typeToString(mEditor->type()) + QLatin1Char('/') + templateName); 0308 0309 if (fileName.isEmpty()) { 0310 KMessageBox::error(q, i18nc("@info", "Unable to find template '%1'.", templateName)); 0311 return; 0312 } 0313 0314 KCalendarCore::ICalFormat format; 0315 if (!format.load(cal, fileName)) { 0316 KMessageBox::error(q, i18nc("@info", "Error loading template file '%1'.", fileName)); 0317 return; 0318 } 0319 0320 KCalendarCore::Incidence::List incidences = cal->incidences(); 0321 if (incidences.isEmpty()) { 0322 KMessageBox::error(q, i18nc("@info", "Template does not contain a valid incidence.")); 0323 return; 0324 } 0325 0326 mIeDateTime->setActiveDate(QDate()); 0327 KCalendarCore::Incidence::Ptr newInc = KCalendarCore::Incidence::Ptr(incidences.first()->clone()); 0328 newInc->setUid(KCalendarCore::CalFormat::createUniqueId()); 0329 0330 // We add a custom property so that some fields aren't loaded, dates for example 0331 newInc->setCustomProperty(QByteArray("kdepim"), "isTemplate", QStringLiteral("true")); 0332 mEditor->load(newInc); 0333 newInc->removeCustomProperty(QByteArray(), "isTemplate"); 0334 } 0335 0336 void IncidenceDialogPrivate::manageTemplates() 0337 { 0338 Q_Q(IncidenceDialog); 0339 0340 QStringList &templates = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type()); 0341 0342 QPointer<IncidenceEditorNG::TemplateManagementDialog> dialog( 0343 new IncidenceEditorNG::TemplateManagementDialog(q, templates, KCalUtils::Stringify::incidenceType(mEditor->type()))); 0344 0345 q->connect(dialog, &TemplateManagementDialog::loadTemplate, q, [this](const QString &templateName) { 0346 loadTemplate(templateName); 0347 }); 0348 q->connect(dialog, &TemplateManagementDialog::templatesChanged, q, [this](const QStringList &templates) { 0349 storeTemplatesInConfig(templates); 0350 }); 0351 q->connect(dialog, &TemplateManagementDialog::saveTemplate, q, [this](const QString &templateName) { 0352 saveTemplate(templateName); 0353 }); 0354 dialog->exec(); 0355 delete dialog; 0356 } 0357 0358 void IncidenceDialogPrivate::saveTemplate(const QString &templateName) 0359 { 0360 Q_ASSERT(!templateName.isEmpty()); 0361 0362 KCalendarCore::MemoryCalendar::Ptr cal(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone())); 0363 0364 switch (mEditor->type()) { 0365 case KCalendarCore::Incidence::TypeEvent: { 0366 KCalendarCore::Event::Ptr event(new KCalendarCore::Event()); 0367 mEditor->save(event); 0368 cal->addEvent(KCalendarCore::Event::Ptr(event->clone())); 0369 break; 0370 } 0371 case KCalendarCore::Incidence::TypeTodo: { 0372 KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo); 0373 mEditor->save(todo); 0374 cal->addTodo(KCalendarCore::Todo::Ptr(todo->clone())); 0375 break; 0376 } 0377 case KCalendarCore::Incidence::TypeJournal: { 0378 KCalendarCore::Journal::Ptr journal(new KCalendarCore::Journal); 0379 mEditor->save(journal); 0380 cal->addJournal(KCalendarCore::Journal::Ptr(journal->clone())); 0381 break; 0382 } 0383 default: 0384 Q_ASSERT_X(false, "saveTemplate", "Fix your program"); 0385 } 0386 0387 QString fileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/korganizer/templates/") 0388 + typeToString(mEditor->type()) + QLatin1Char('/'); 0389 QDir().mkpath(fileName); 0390 fileName += templateName; 0391 0392 KCalendarCore::ICalFormat format; 0393 format.save(cal, fileName); 0394 } 0395 0396 void IncidenceDialogPrivate::storeTemplatesInConfig(const QStringList &templateNames) 0397 { 0398 // I find this somewhat broken. templates() returns a reference, maybe it should 0399 // be changed by adding a setTemplates method. 0400 const QStringList origTemplates = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type()); 0401 const QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("korganizer/templates/") 0402 + typeToString(mEditor->type()) + QLatin1Char('/'); 0403 QDir().mkpath(defaultPath); 0404 for (const QString &tmpl : origTemplates) { 0405 if (!templateNames.contains(tmpl)) { 0406 const QString fileName = defaultPath + tmpl; 0407 QFile file(fileName); 0408 if (file.exists()) { 0409 file.remove(); 0410 } 0411 } 0412 } 0413 0414 IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type()) = templateNames; 0415 IncidenceEditorNG::EditorConfig::instance()->config()->save(); 0416 } 0417 0418 void IncidenceDialogPrivate::updateAttachmentCount(int newCount) 0419 { 0420 if (newCount > 0) { 0421 mUi->mTabWidget->setTabText(AttachmentsTab, i18nc("@title:tab Tab to modify attachments of an event or todo", "Attac&hments (%1)", newCount)); 0422 } else { 0423 mUi->mTabWidget->setTabText(AttachmentsTab, i18nc("@title:tab Tab to modify attachments of an event or todo", "Attac&hments")); 0424 } 0425 } 0426 0427 void IncidenceDialogPrivate::updateAttendeeCount(int newCount) 0428 { 0429 if (newCount > 0) { 0430 mUi->mTabWidget->setTabText(AttendeesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Attendees (%1)", newCount)); 0431 } else { 0432 mUi->mTabWidget->setTabText(AttendeesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Attendees")); 0433 } 0434 } 0435 0436 void IncidenceDialogPrivate::updateResourceCount(int newCount) 0437 { 0438 if (newCount > 0) { 0439 mUi->mTabWidget->setTabText(ResourcesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Resources (%1)", newCount)); 0440 } else { 0441 mUi->mTabWidget->setTabText(ResourcesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Resources")); 0442 } 0443 } 0444 0445 void IncidenceDialogPrivate::updateButtonStatus(bool isDirty) 0446 { 0447 mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty || mInitiallyDirty); 0448 } 0449 0450 bool IncidenceDialogPrivate::containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const 0451 { 0452 return partIdentifiers.contains(QByteArray("PLD:RFC822")); 0453 } 0454 0455 void IncidenceDialogPrivate::handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage) 0456 { 0457 Q_Q(IncidenceDialog); 0458 0459 bool retry = false; 0460 0461 if (!errorMessage.isEmpty()) { 0462 const QString message = i18nc("@info", 0463 "Unable to store the incidence in the calendar. Try again?\n\n " 0464 "Reason: %1", 0465 errorMessage); 0466 const int answer = KMessageBox::warningTwoActions(q, 0467 message, 0468 QString(), 0469 KGuiItem(i18nc("@action:button", "Retry"), QStringLiteral("dialog-ok")), 0470 KStandardGuiItem::cancel()); 0471 retry = (answer == KMessageBox::ButtonCode::PrimaryAction); 0472 } 0473 0474 if (retry) { 0475 mItemManager->save(); 0476 } else { 0477 updateButtonStatus(isDirty()); 0478 mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); 0479 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true); 0480 } 0481 } 0482 0483 void IncidenceDialogPrivate::handleItemSaveFinish(EditorItemManager::SaveAction saveAction) 0484 { 0485 Q_Q(IncidenceDialog); 0486 0487 if ((mEditor->type() == KCalendarCore::Incidence::TypeEvent) && (mCalSelector->count() > 1) 0488 && (CalendarSupport::KCalPrefs::instance()->defaultCalendarId() == -1)) { 0489 const QString collectionName = mCalSelector->currentText(); 0490 const QString message = xi18nc("@info", 0491 "<para>You have not set a default calendar for your events yet.</para>" 0492 "<para>Setting a default calendar will make creating new events faster and " 0493 "easier with less chance of filing them into the wrong folder.</para>" 0494 "<para>Would you like to set your default events calendar to " 0495 "<resource>%1</resource>?</para>", 0496 collectionName); 0497 const int answer = KMessageBox::questionTwoActions(q, 0498 message, 0499 i18nc("@title:window", "Set Default Calendar?"), 0500 KGuiItem(i18nc("@action:button", "Set As Default"), QStringLiteral("dialog-ok")), 0501 KGuiItem(i18nc("@action:button", "Do Not Set"), QStringLiteral("dialog-cancel")), 0502 QStringLiteral("setDefaultCalendarCollection")); 0503 if (answer == KMessageBox::ButtonCode::PrimaryAction) { 0504 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId(mItem.storageCollectionId()); 0505 } 0506 } 0507 0508 if (mCloseOnSave) { 0509 q->accept(); 0510 } else { 0511 const Akonadi::Item item = mItemManager->item(); 0512 Q_ASSERT(item.isValid()); 0513 Q_ASSERT(item.hasPayload()); 0514 Q_ASSERT(item.hasPayload<KCalendarCore::Incidence::Ptr>()); 0515 // Now the item is successfully saved, reload it in the editor in order to 0516 // reset the dirty status of the editor. 0517 mEditor->load(item.payload<KCalendarCore::Incidence::Ptr>()); 0518 mEditor->load(item); 0519 0520 // Set the buttons to a reasonable state as well (ok and apply should be 0521 // disabled at this point). 0522 mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); 0523 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true); 0524 mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty()); 0525 } 0526 0527 if (saveAction == EditorItemManager::Create) { 0528 Q_EMIT q->incidenceCreated(mItemManager->item()); 0529 } 0530 } 0531 0532 bool IncidenceDialogPrivate::hasSupportedPayload(const Akonadi::Item &item) const 0533 { 0534 return !Akonadi::CalendarUtils::incidence(item).isNull(); 0535 } 0536 0537 bool IncidenceDialogPrivate::isDirty() const 0538 { 0539 if (mItem.isValid()) { 0540 return mEditor->isDirty() || mCalSelector->currentCollection().id() != mItem.storageCollectionId(); 0541 } else { 0542 return mEditor->isDirty(); 0543 } 0544 } 0545 0546 bool IncidenceDialogPrivate::isValid() const 0547 { 0548 Q_Q(const IncidenceDialog); 0549 if (mEditor->isValid()) { 0550 // Check if there's a selected collection. 0551 if (mCalSelector->currentCollection().isValid()) { 0552 return true; 0553 } else { 0554 qCWarning(INCIDENCEEDITOR_LOG) << "Select a collection first"; 0555 Q_EMIT q->invalidCollection(); 0556 } 0557 } 0558 0559 return false; 0560 } 0561 0562 void IncidenceDialogPrivate::load(const Akonadi::Item &item) 0563 { 0564 Q_Q(IncidenceDialog); 0565 0566 Q_ASSERT(hasSupportedPayload(item)); 0567 0568 if (CalendarSupport::hasJournal(item)) { 0569 // mUi->mTabWidget->removeTab(5); 0570 mUi->mTabWidget->removeTab(AttachmentsTab); 0571 mUi->mTabWidget->removeTab(RecurrenceTab); 0572 mUi->mTabWidget->removeTab(AlarmsTab); 0573 mUi->mTabWidget->removeTab(AttendeesTab); 0574 mUi->mTabWidget->removeTab(ResourcesTab); 0575 } 0576 0577 mEditor->load(Akonadi::CalendarUtils::incidence(item)); 0578 mEditor->load(item); 0579 0580 const KCalendarCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); 0581 const QStringList allEmails = IncidenceEditorNG::EditorConfig::instance()->allEmails(); 0582 const KCalendarCore::Attendee me = incidence->attendeeByMails(allEmails); 0583 0584 if (incidence->attendeeCount() > 1 // >1 because you won't drink alone 0585 && !me.isNull() 0586 && (me.status() == KCalendarCore::Attendee::NeedsAction || me.status() == KCalendarCore::Attendee::Tentative 0587 || me.status() == KCalendarCore::Attendee::InProcess)) { 0588 // Show the invitation bar: "You are invited [accept] [decline]" 0589 mUi->mInvitationBar->show(); 0590 } else { 0591 mUi->mInvitationBar->hide(); 0592 } 0593 0594 qCDebug(INCIDENCEEDITOR_LOG) << "Loading item " << item.id() << "; parent " << item.parentCollection().id() << "; storage " << item.storageCollectionId(); 0595 0596 if (item.storageCollectionId() > -1) { 0597 mCalSelector->setDefaultCollection(Akonadi::Collection(item.storageCollectionId())); 0598 } 0599 0600 if (!mCalSelector->mimeTypeFilter().contains(incidence->mimeType())) { 0601 mCalSelector->setMimeTypeFilter({incidence->mimeType()}); 0602 } 0603 0604 if (mEditor->type() == KCalendarCore::Incidence::TypeTodo) { 0605 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-tasks"))); 0606 } else if (mEditor->type() == KCalendarCore::Incidence::TypeEvent) { 0607 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-day"))); 0608 } else if (mEditor->type() == KCalendarCore::Incidence::TypeJournal) { 0609 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-pim-journal"))); 0610 } 0611 0612 // Initialize tab's titles 0613 updateAttachmentCount(incidence->attachments().size()); 0614 updateResourceCount(mIeResource->resourceCount()); 0615 updateAttendeeCount(mIeAttendee->attendeeCount()); 0616 handleRecurrenceChange(mIeRecurrence->currentRecurrenceType()); 0617 handleAlarmCountChange(incidence->alarms().count()); 0618 0619 mItem = item; 0620 0621 q->show(); 0622 } 0623 0624 Akonadi::Item IncidenceDialogPrivate::save(const Akonadi::Item &item) 0625 { 0626 Q_ASSERT(mEditor->incidence<KCalendarCore::Incidence>()); 0627 0628 KCalendarCore::Incidence::Ptr incidenceInEditor = mEditor->incidence<KCalendarCore::Incidence>(); 0629 KCalendarCore::Incidence::Ptr newIncidence(incidenceInEditor->clone()); 0630 0631 Akonadi::Item result = item; 0632 result.setMimeType(newIncidence->mimeType()); 0633 0634 // There's no editor that has the relatedTo property. We must set it here, by hand. 0635 // Otherwise it gets lost. 0636 // FIXME: Why don't we clone() incidenceInEditor then pass the clone to save(), 0637 // I wonder if we're not leaking other properties. 0638 newIncidence->setRelatedTo(incidenceInEditor->relatedTo()); 0639 0640 mEditor->save(newIncidence); 0641 mEditor->save(result); 0642 0643 // Make sure that we don't loose uid for existing incidence 0644 newIncidence->setUid(mEditor->incidence<KCalendarCore::Incidence>()->uid()); 0645 0646 // Mark the incidence as changed 0647 if (mItem.isValid()) { 0648 newIncidence->setRevision(newIncidence->revision() + 1); 0649 } 0650 0651 result.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence); 0652 return result; 0653 } 0654 0655 Akonadi::Collection IncidenceDialogPrivate::selectedCollection() const 0656 { 0657 return mCalSelector->currentCollection(); 0658 } 0659 0660 void IncidenceDialogPrivate::reject(RejectReason reason, const QString &errorMessage) 0661 { 0662 Q_UNUSED(reason) 0663 0664 Q_Q(IncidenceDialog); 0665 qCCritical(INCIDENCEEDITOR_LOG) << "Rejecting:" << errorMessage; 0666 q->deleteLater(); 0667 } 0668 0669 /// IncidenceDialog 0670 0671 IncidenceDialog::IncidenceDialog(Akonadi::IncidenceChanger *changer, QWidget *parent, Qt::WindowFlags flags) 0672 : QDialog(parent, flags) 0673 , d_ptr(new IncidenceDialogPrivate(changer, this)) 0674 { 0675 Q_D(IncidenceDialog); 0676 setAttribute(Qt::WA_DeleteOnClose); 0677 0678 d->mUi->mTabWidget->setCurrentIndex(0); 0679 d->mUi->mSummaryEdit->setFocus(); 0680 0681 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip", "Save current changes")); 0682 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@action:button", "Save changes and close dialog")); 0683 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setToolTip(i18nc("@action:button", "Discard changes and close dialog")); 0684 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); 0685 0686 auto defaultButton = d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults); 0687 defaultButton->setText(i18nc("@action:button", "&Templates...")); 0688 defaultButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template"))); 0689 defaultButton->setToolTip(i18nc("@info:tooltip", "Manage templates for this item")); 0690 defaultButton->setWhatsThis(i18nc("@info:whatsthis", 0691 "Push this button to show a dialog that helps " 0692 "you manage a set of templates. Templates " 0693 "can make creating new items easier and faster " 0694 "by putting your favorite default values into " 0695 "the editor automatically.")); 0696 0697 connect(d->mUi->buttonBox, &QDialogButtonBox::clicked, this, &IncidenceDialog::slotButtonClicked); 0698 0699 setModal(false); 0700 0701 connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked, d->mIeAttendee, &IncidenceAttendee::acceptForMe); 0702 connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked, d->mUi->mInvitationBar, &QWidget::hide); 0703 connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked, d->mIeAttendee, &IncidenceAttendee::declineForMe); 0704 connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked, d->mUi->mInvitationBar, &QWidget::hide); 0705 connect(this, &IncidenceDialog::invalidCollection, this, [d]() { 0706 d->slotInvalidCollection(); 0707 }); 0708 readConfig(); 0709 } 0710 0711 IncidenceDialog::~IncidenceDialog() 0712 { 0713 writeConfig(); 0714 } 0715 0716 void IncidenceDialog::writeConfig() 0717 { 0718 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(myIncidenceDialogConfigGroupName)); 0719 KWindowConfig::saveWindowSize(windowHandle(), group); 0720 } 0721 0722 void IncidenceDialog::readConfig() 0723 { 0724 create(); // ensure a window is created 0725 windowHandle()->resize(QSize(500, 500)); 0726 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(myIncidenceDialogConfigGroupName)); 0727 KWindowConfig::restoreWindowSize(windowHandle(), group); 0728 resize(windowHandle()->size()); // workaround for QTBUG-40584 0729 } 0730 0731 void IncidenceDialog::load(const Akonadi::Item &item, const QDate &activeDate) 0732 { 0733 Q_D(IncidenceDialog); 0734 d->mIeDateTime->setActiveDate(activeDate); 0735 if (item.isValid()) { // We're editing 0736 d->mItemManager->load(item); 0737 } else { // We're creating 0738 Q_ASSERT(d->hasSupportedPayload(item)); 0739 d->load(item); 0740 show(); 0741 } 0742 } 0743 0744 void IncidenceDialog::selectCollection(const Akonadi::Collection &collection) 0745 { 0746 Q_D(IncidenceDialog); 0747 d->setCalendarCollection(collection); 0748 } 0749 0750 void IncidenceDialog::setIsCounterProposal(bool isCounterProposal) 0751 { 0752 Q_D(IncidenceDialog); 0753 d->mItemManager->setIsCounterProposal(isCounterProposal); 0754 } 0755 0756 QObject *IncidenceDialog::typeAheadReceiver() const 0757 { 0758 Q_D(const IncidenceDialog); 0759 return d->mUi->mSummaryEdit; 0760 } 0761 0762 void IncidenceDialog::slotButtonClicked(QAbstractButton *button) 0763 { 0764 Q_D(IncidenceDialog); 0765 0766 if (d->mUi->buttonBox->button(QDialogButtonBox::Ok) == button) { 0767 if (d->isDirty() || d->mInitiallyDirty) { 0768 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 0769 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); 0770 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); 0771 d->mCloseOnSave = true; 0772 d->mInitiallyDirty = false; 0773 d->mItemManager->save(toItemManagerFlags(d->mUi->mSignItip->isChecked(), d->mUi->mEncryptItip->isChecked())); 0774 } else { 0775 close(); 0776 } 0777 } else if (d->mUi->buttonBox->button(QDialogButtonBox::Apply) == button) { 0778 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 0779 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); 0780 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); 0781 0782 d->mCloseOnSave = false; 0783 d->mInitiallyDirty = false; 0784 d->mItemManager->save(toItemManagerFlags(d->mUi->mSignItip->isChecked(), d->mUi->mEncryptItip->isChecked())); 0785 } else if (d->mUi->buttonBox->button(QDialogButtonBox::Cancel) == button) { 0786 if (d->isDirty() 0787 && KMessageBox::questionTwoActions(this, 0788 i18nc("@info", "Do you really want to cancel?"), 0789 i18nc("@title:window", "KOrganizer Confirmation"), 0790 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")), 0791 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel"))) 0792 == KMessageBox::ButtonCode::PrimaryAction) { 0793 QDialog::reject(); // Discard current changes 0794 } else if (!d->isDirty()) { 0795 QDialog::reject(); // No pending changes, just close the dialog. 0796 } // else { // the user wasn't finished editing after all } 0797 } else if (d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { 0798 d->manageTemplates(); 0799 } else { 0800 Q_ASSERT(false); // Shouldn't happen 0801 } 0802 } 0803 0804 void IncidenceDialog::reject() 0805 { 0806 Q_D(IncidenceDialog); 0807 if (d->isDirty() 0808 && KMessageBox::questionTwoActions(this, 0809 i18nc("@info", "Do you really want to cancel?"), 0810 i18nc("@title:window", "KOrganizer Confirmation"), 0811 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")), 0812 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel"))) 0813 == KMessageBox::ButtonCode::PrimaryAction) { 0814 QDialog::reject(); // Discard current changes 0815 } else if (!d->isDirty()) { 0816 QDialog::reject(); // No pending changes, just close the dialog. 0817 } 0818 } 0819 0820 void IncidenceDialog::closeEvent(QCloseEvent *event) 0821 { 0822 Q_D(IncidenceDialog); 0823 if (d->isDirty() 0824 && KMessageBox::questionTwoActions(this, 0825 i18nc("@info", "Do you really want to cancel?"), 0826 i18nc("@title:window", "KOrganizer Confirmation"), 0827 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")), 0828 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel"))) 0829 == KMessageBox::ButtonCode::PrimaryAction) { 0830 QDialog::reject(); // Discard current changes 0831 QDialog::closeEvent(event); 0832 } else if (!d->isDirty()) { 0833 QDialog::reject(); // No pending changes, just close the dialog. 0834 QDialog::closeEvent(event); 0835 } else { 0836 event->ignore(); 0837 } 0838 } 0839 0840 void IncidenceDialog::setInitiallyDirty(bool initiallyDirty) 0841 { 0842 Q_D(IncidenceDialog); 0843 d->mInitiallyDirty = initiallyDirty; 0844 } 0845 0846 Akonadi::Item IncidenceDialog::item() const 0847 { 0848 Q_D(const IncidenceDialog); 0849 return d->mItemManager->item(); 0850 } 0851 0852 void IncidenceDialog::handleSelectedCollectionChange(const Akonadi::Collection &collection) 0853 { 0854 Q_D(IncidenceDialog); 0855 if (d->mItem.parentCollection().isValid()) { 0856 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(collection.id() != d->mItem.parentCollection().id()); 0857 } 0858 } 0859 0860 #include "moc_incidencedialog.cpp"