Incidenceeditor

incidencedialog.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org>
3 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
4 SPDX-FileCopyrightText: 2012 Allen Winter <winter@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "incidencedialog.h"
10#include "combinedincidenceeditor.h"
11#include "editorconfig.h"
12#include "incidencealarm.h"
13#include "incidenceattachment.h"
14#include "incidenceattendee.h"
15#include "incidencecategories.h"
16#include "incidencecompletionpriority.h"
17#include "incidencedatetime.h"
18#include "incidencedescription.h"
19#include "incidenceeditor_debug.h"
20#include "incidencerecurrence.h"
21#include "incidenceresource.h"
22#include "incidencesecrecy.h"
23#include "incidencewhatwhere.h"
24#include "templatemanagementdialog.h"
25#include "ui_dialogdesktop.h"
26
27#include "incidenceeditorsettings.h"
28
29#include <CalendarSupport/KCalPrefs>
30#include <CalendarSupport/Utils>
31
32#include <Akonadi/CalendarUtils>
33#include <Akonadi/CollectionComboBox>
34#include <Akonadi/ETMCalendar>
35#include <Akonadi/EntityTreeModel>
36#include <Akonadi/Item>
37
38#include <KCalUtils/Stringify>
39#include <KCalendarCore/ICalFormat>
40#include <KCalendarCore/MemoryCalendar>
41
42#include <KMessageBox>
43#include <KSharedConfig>
44
45#include <KWindowConfig>
46#include <QCloseEvent>
47#include <QDir>
48#include <QIcon>
49#include <QStandardPaths>
50#include <QTimeZone>
51#include <QWindow>
52
53using namespace IncidenceEditorNG;
54namespace
55{
56static const char myIncidenceDialogConfigGroupName[] = "IncidenceDialog";
57
58IncidenceEditorNG::EditorItemManager::ItipPrivacyFlags toItemManagerFlags(bool sign, bool encrypt)
59{
61 flags.setFlag(IncidenceEditorNG::EditorItemManager::ItipPrivacySign, sign);
62 flags.setFlag(IncidenceEditorNG::EditorItemManager::ItipPrivacyEncrypt, encrypt);
63 return flags;
64}
65}
66namespace IncidenceEditorNG
67{
68enum Tabs { GeneralTab = 0, AttendeesTab, ResourcesTab, AlarmsTab, RecurrenceTab, AttachmentsTab };
69
70class IncidenceDialogPrivate : public ItemEditorUi
71{
72 IncidenceDialog *q_ptr;
73 Q_DECLARE_PUBLIC(IncidenceDialog)
74
75public:
76 Ui::EventOrTodoDesktop *mUi = nullptr;
77 Akonadi::CollectionComboBox *mCalSelector = nullptr;
78 bool mCloseOnSave = false;
79
80 EditorItemManager *mItemManager = nullptr;
81 CombinedIncidenceEditor *mEditor = nullptr;
82 IncidenceDateTime *mIeDateTime = nullptr;
83 IncidenceAttendee *mIeAttendee = nullptr;
84 IncidenceRecurrence *mIeRecurrence = nullptr;
85 IncidenceResource *mIeResource = nullptr;
86 bool mInitiallyDirty = false;
87 Akonadi::Item mItem;
88 [[nodiscard]] QString typeToString(const int type) const;
89
90public:
91 IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq);
92 ~IncidenceDialogPrivate() override;
93
94 /// General methods
95 void handleAlarmCountChange(int newCount);
96 void handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type);
97 void loadTemplate(const QString &templateName);
98 void manageTemplates();
99 void saveTemplate(const QString &templateName);
100 void storeTemplatesInConfig(const QStringList &newTemplates);
101 void updateAttachmentCount(int newCount);
102 void updateAttendeeCount(int newCount);
103 void updateResourceCount(int newCount);
104 void updateButtonStatus(bool isDirty);
105 void showMessage(const QString &text, KMessageWidget::MessageType type);
106 void slotInvalidCollection();
107 void setCalendarCollection(const Akonadi::Collection &collection);
108
109 /// ItemEditorUi methods
110 [[nodiscard]] bool containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const override;
111 void handleItemSaveFinish(EditorItemManager::SaveAction);
112 void handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage);
113 [[nodiscard]] bool hasSupportedPayload(const Akonadi::Item &item) const override;
114 [[nodiscard]] bool isDirty() const override;
115 [[nodiscard]] bool isValid() const override;
116 void load(const Akonadi::Item &item) override;
117 Akonadi::Item save(const Akonadi::Item &item) override;
118 [[nodiscard]] Akonadi::Collection selectedCollection() const override;
119
120 void reject(RejectReason reason, const QString &errorMessage = QString()) override;
121};
122}
123
124IncidenceDialogPrivate::IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq)
125 : q_ptr(qq)
126 , mUi(new Ui::EventOrTodoDesktop)
127 , mCalSelector(new Akonadi::CollectionComboBox(changer ? changer->entityTreeModel() : nullptr))
128 , mItemManager(new EditorItemManager(this, changer))
129 , mEditor(new CombinedIncidenceEditor(qq))
130{
131 Q_Q(IncidenceDialog);
132 mUi->setupUi(q);
133 mUi->mMessageWidget->hide();
134 auto layout = new QGridLayout(mUi->mCalSelectorPlaceHolder);
135 layout->setSpacing(0);
136 layout->setContentsMargins(0, 0, 0, 0);
137 layout->addWidget(mCalSelector);
138 mCalSelector->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
139 mUi->label->setBuddy(mCalSelector);
140 q->connect(mCalSelector, &Akonadi::CollectionComboBox::currentChanged, q, &IncidenceDialog::handleSelectedCollectionChange);
141
142 // Now instantiate the logic of the dialog. These editors update the ui, validate
143 // fields and load/store incidences in the ui.
144 auto ieGeneral = new IncidenceWhatWhere(mUi);
145 mEditor->combine(ieGeneral);
146
147 auto ieCategories = new IncidenceCategories(mUi);
148 mEditor->combine(ieCategories);
149
150 mIeDateTime = new IncidenceDateTime(mUi);
151 mEditor->combine(mIeDateTime);
152
153 auto ieCompletionPriority = new IncidenceCompletionPriority(mUi);
154 mEditor->combine(ieCompletionPriority);
155
156 auto ieDescription = new IncidenceDescription(mUi);
157 mEditor->combine(ieDescription);
158
159 auto ieAlarm = new IncidenceAlarm(mIeDateTime, mUi);
160 mEditor->combine(ieAlarm);
161
162 auto ieAttachments = new IncidenceAttachment(mUi);
163 mEditor->combine(ieAttachments);
164
165 mIeRecurrence = new IncidenceRecurrence(mIeDateTime, mUi);
166 mEditor->combine(mIeRecurrence);
167
168 auto ieSecrecy = new IncidenceSecrecy(mUi);
169 mEditor->combine(ieSecrecy);
170
171 mIeAttendee = new IncidenceAttendee(qq, mIeDateTime, mUi);
172 mIeAttendee->setParent(qq);
173 mEditor->combine(mIeAttendee);
174
175 mIeResource = new IncidenceResource(mIeAttendee, mIeDateTime, mUi);
176 mEditor->combine(mIeResource);
177
178 // Set the default collection
179 const qint64 colId = CalendarSupport::KCalPrefs::instance()->defaultCalendarId();
180 const Akonadi::Collection col(colId);
181 setCalendarCollection(col);
182
183 q->connect(mEditor, &CombinedIncidenceEditor::showMessage, q, [this](const QString &reason, KMessageWidget::MessageType msgType) {
184 showMessage(reason, msgType);
185 });
186 q->connect(mEditor, &IncidenceEditor::dirtyStatusChanged, q, [this](bool isDirty) {
187 updateButtonStatus(isDirty);
188 });
189 q->connect(mItemManager, &EditorItemManager::itemSaveFinished, q, [this](EditorItemManager::SaveAction action) {
190 handleItemSaveFinish(action);
191 });
192 q->connect(mItemManager, &EditorItemManager::itemSaveFailed, q, [this](EditorItemManager::SaveAction action, const QString &message) {
193 handleItemSaveFail(action, message);
194 });
195 q->connect(ieAlarm, &IncidenceAlarm::alarmCountChanged, q, [this](int newCount) {
196 handleAlarmCountChange(newCount);
197 });
198 q->connect(mIeRecurrence, &IncidenceRecurrence::recurrenceChanged, q, [this](IncidenceEditorNG::RecurrenceType type) {
199 handleRecurrenceChange(type);
200 });
201 q->connect(ieAttachments, &IncidenceAttachment::attachmentCountChanged, q, [this](int newCount) {
202 updateAttachmentCount(newCount);
203 });
204 q->connect(mIeAttendee, &IncidenceAttendee::attendeeCountChanged, q, [this](int count) {
205 updateAttendeeCount(count);
206 });
207 q->connect(mIeResource, &IncidenceResource::resourceCountChanged, q, [this](int count) {
208 updateResourceCount(count);
209 });
210}
211
212IncidenceDialogPrivate::~IncidenceDialogPrivate()
213{
214 delete mItemManager;
215 delete mEditor;
216 delete mUi;
217}
218
219void IncidenceDialogPrivate::slotInvalidCollection()
220{
221 showMessage(i18n("Select a valid collection first."), KMessageWidget::Warning);
222}
223
224void IncidenceDialogPrivate::setCalendarCollection(const Akonadi::Collection &collection)
225{
226 if (collection.isValid()) {
227 mCalSelector->setDefaultCollection(collection);
228 } else {
229 mCalSelector->setCurrentIndex(0);
230 }
231}
232
233void IncidenceDialogPrivate::showMessage(const QString &text, KMessageWidget::MessageType type)
234{
235 mUi->mMessageWidget->setText(text);
236 mUi->mMessageWidget->setMessageType(type);
237 mUi->mMessageWidget->show();
238}
239
240void IncidenceDialogPrivate::handleAlarmCountChange(int newCount)
241{
242 QString tabText;
243 if (newCount > 0) {
244 tabText = i18nc("@title:tab Tab to configure the reminders of an event or todo", "Reminder (%1)", newCount);
245 } else {
246 tabText = i18nc("@title:tab Tab to configure the reminders of an event or todo", "Reminder");
247 }
248
249 mUi->mTabWidget->setTabText(AlarmsTab, tabText);
250}
251
252void IncidenceDialogPrivate::handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type)
253{
254 QString tabText = i18nc("@title:tab Tab to configure the recurrence of an event or todo", "Rec&urrence");
255
256 // Keep this numbers in sync with the items in mUi->mRecurrenceTypeCombo. I
257 // tried adding an enum to IncidenceRecurrence but for whatever reason I could
258 // Qt not play nice with namespaced enums in signal/slot connections.
259 // Anyways, I don't expect these values to change.
260 switch (type) {
261 case RecurrenceTypeNone:
262 break;
263 case RecurrenceTypeDaily:
264 tabText += i18nc("@title:tab Daily recurring event, capital first letter only", " (D)");
265 break;
266 case RecurrenceTypeWeekly:
267 tabText += i18nc("@title:tab Weekly recurring event, capital first letter only", " (W)");
268 break;
269 case RecurrenceTypeMonthly:
270 tabText += i18nc("@title:tab Monthly recurring event, capital first letter only", " (M)");
271 break;
272 case RecurrenceTypeYearly:
273 tabText += i18nc("@title:tab Yearly recurring event, capital first letter only", " (Y)");
274 break;
275 case RecurrenceTypeException:
276 tabText += i18nc("@title:tab Exception to a recurring event, capital first letter only", " (E)");
277 break;
278 default:
279 Q_ASSERT_X(false, "handleRecurrenceChange", "Fix your program");
280 }
281
282 mUi->mTabWidget->setTabText(RecurrenceTab, tabText);
283}
284
285QString IncidenceDialogPrivate::typeToString(const int type) const
286{
287 // Do not translate.
288 switch (type) {
290 return QStringLiteral("Event");
292 return QStringLiteral("Todo");
294 return QStringLiteral("Journal");
295 default:
296 return QStringLiteral("Unknown");
297 }
298}
299
300void IncidenceDialogPrivate::loadTemplate(const QString &templateName)
301{
302 Q_Q(IncidenceDialog);
303
305
307 QStringLiteral("/korganizer/templates/") + typeToString(mEditor->type()) + QLatin1Char('/') + templateName);
308
309 if (fileName.isEmpty()) {
310 KMessageBox::error(q, i18nc("@info", "Unable to find template '%1'.", templateName));
311 return;
312 }
313
315 if (!format.load(cal, fileName)) {
316 KMessageBox::error(q, i18nc("@info", "Error loading template file '%1'.", fileName));
317 return;
318 }
319
320 KCalendarCore::Incidence::List incidences = cal->incidences();
321 if (incidences.isEmpty()) {
322 KMessageBox::error(q, i18nc("@info", "Template does not contain a valid incidence."));
323 return;
324 }
325
326 mIeDateTime->setActiveDate(QDate());
329
330 // We add a custom property so that some fields aren't loaded, dates for example
331 newInc->setCustomProperty(QByteArray("kdepim"), "isTemplate", QStringLiteral("true"));
332 mEditor->load(newInc);
333 newInc->removeCustomProperty(QByteArray(), "isTemplate");
334}
335
336void IncidenceDialogPrivate::manageTemplates()
337{
338 Q_Q(IncidenceDialog);
339
340 QStringList &templates = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type());
341
343 new IncidenceEditorNG::TemplateManagementDialog(q, templates, KCalUtils::Stringify::incidenceType(mEditor->type())));
344
345 q->connect(dialog, &TemplateManagementDialog::loadTemplate, q, [this](const QString &templateName) {
346 loadTemplate(templateName);
347 });
348 q->connect(dialog, &TemplateManagementDialog::templatesChanged, q, [this](const QStringList &templates) {
349 storeTemplatesInConfig(templates);
350 });
351 q->connect(dialog, &TemplateManagementDialog::saveTemplate, q, [this](const QString &templateName) {
352 saveTemplate(templateName);
353 });
354 dialog->exec();
355 delete dialog;
356}
357
358void IncidenceDialogPrivate::saveTemplate(const QString &templateName)
359{
360 Q_ASSERT(!templateName.isEmpty());
361
363
364 switch (mEditor->type()) {
367 mEditor->save(event);
368 cal->addEvent(KCalendarCore::Event::Ptr(event->clone()));
369 break;
370 }
373 mEditor->save(todo);
374 cal->addTodo(KCalendarCore::Todo::Ptr(todo->clone()));
375 break;
376 }
379 mEditor->save(journal);
380 cal->addJournal(KCalendarCore::Journal::Ptr(journal->clone()));
381 break;
382 }
383 default:
384 Q_ASSERT_X(false, "saveTemplate", "Fix your program");
385 }
386
387 QString fileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/korganizer/templates/")
388 + typeToString(mEditor->type()) + QLatin1Char('/');
389 QDir().mkpath(fileName);
390 fileName += templateName;
391
393 format.save(cal, fileName);
394}
395
396void IncidenceDialogPrivate::storeTemplatesInConfig(const QStringList &templateNames)
397{
398 // I find this somewhat broken. templates() returns a reference, maybe it should
399 // be changed by adding a setTemplates method.
400 const QStringList origTemplates = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type());
401 const QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("korganizer/templates/")
402 + typeToString(mEditor->type()) + QLatin1Char('/');
403 QDir().mkpath(defaultPath);
404 for (const QString &tmpl : origTemplates) {
405 if (!templateNames.contains(tmpl)) {
406 const QString fileName = defaultPath + tmpl;
407 QFile file(fileName);
408 if (file.exists()) {
409 file.remove();
410 }
411 }
412 }
413
414 IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type()) = templateNames;
415 IncidenceEditorNG::EditorConfig::instance()->config()->save();
416}
417
418void IncidenceDialogPrivate::updateAttachmentCount(int newCount)
419{
420 if (newCount > 0) {
421 mUi->mTabWidget->setTabText(AttachmentsTab, i18nc("@title:tab Tab to modify attachments of an event or todo", "Attac&hments (%1)", newCount));
422 } else {
423 mUi->mTabWidget->setTabText(AttachmentsTab, i18nc("@title:tab Tab to modify attachments of an event or todo", "Attac&hments"));
424 }
425}
426
427void IncidenceDialogPrivate::updateAttendeeCount(int newCount)
428{
429 if (newCount > 0) {
430 mUi->mTabWidget->setTabText(AttendeesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Attendees (%1)", newCount));
431 } else {
432 mUi->mTabWidget->setTabText(AttendeesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Attendees"));
433 }
434}
435
436void IncidenceDialogPrivate::updateResourceCount(int newCount)
437{
438 if (newCount > 0) {
439 mUi->mTabWidget->setTabText(ResourcesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Resources (%1)", newCount));
440 } else {
441 mUi->mTabWidget->setTabText(ResourcesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Resources"));
442 }
443}
444
445void IncidenceDialogPrivate::updateButtonStatus(bool isDirty)
446{
447 mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty || mInitiallyDirty);
448}
449
450bool IncidenceDialogPrivate::containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const
451{
452 return partIdentifiers.contains(QByteArray("PLD:RFC822"));
453}
454
455void IncidenceDialogPrivate::handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage)
456{
457 Q_Q(IncidenceDialog);
458
459 bool retry = false;
460
461 if (!errorMessage.isEmpty()) {
462 const QString message = i18nc("@info",
463 "Unable to store the incidence in the calendar. Try again?\n\n "
464 "Reason: %1",
465 errorMessage);
466 const int answer = KMessageBox::warningTwoActions(q,
467 message,
468 QString(),
469 KGuiItem(i18nc("@action:button", "Retry"), QStringLiteral("dialog-ok")),
471 retry = (answer == KMessageBox::ButtonCode::PrimaryAction);
472 }
473
474 if (retry) {
475 mItemManager->save();
476 } else {
477 updateButtonStatus(isDirty());
478 mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
479 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
480 }
481}
482
483void IncidenceDialogPrivate::handleItemSaveFinish(EditorItemManager::SaveAction saveAction)
484{
485 Q_Q(IncidenceDialog);
486
487 if ((mEditor->type() == KCalendarCore::Incidence::TypeEvent) && (mCalSelector->count() > 1)
488 && (CalendarSupport::KCalPrefs::instance()->defaultCalendarId() == -1)) {
489 const QString collectionName = mCalSelector->currentText();
490 const QString message = xi18nc("@info",
491 "<para>You have not set a default calendar for your events yet.</para>"
492 "<para>Setting a default calendar will make creating new events faster and "
493 "easier with less chance of filing them into the wrong folder.</para>"
494 "<para>Would you like to set your default events calendar to "
495 "<resource>%1</resource>?</para>",
496 collectionName);
497 const int answer = KMessageBox::questionTwoActions(q,
498 message,
499 i18nc("@title:window", "Set Default Calendar?"),
500 KGuiItem(i18nc("@action:button", "Set As Default"), QStringLiteral("dialog-ok")),
501 KGuiItem(i18nc("@action:button", "Do Not Set"), QStringLiteral("dialog-cancel")),
502 QStringLiteral("setDefaultCalendarCollection"));
503 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
504 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId(mItem.storageCollectionId());
505 }
506 }
507
508 if (mCloseOnSave) {
509 q->accept();
510 } else {
511 const Akonadi::Item item = mItemManager->item();
512 Q_ASSERT(item.isValid());
513 Q_ASSERT(item.hasPayload());
515 // Now the item is successfully saved, reload it in the editor in order to
516 // reset the dirty status of the editor.
518 mEditor->load(item);
519
520 // Set the buttons to a reasonable state as well (ok and apply should be
521 // disabled at this point).
522 mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
523 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
524 mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty());
525 }
526
527 if (saveAction == EditorItemManager::Create) {
528 Q_EMIT q->incidenceCreated(mItemManager->item());
529 }
530}
531
532bool IncidenceDialogPrivate::hasSupportedPayload(const Akonadi::Item &item) const
533{
535}
536
537bool IncidenceDialogPrivate::isDirty() const
538{
539 if (mItem.isValid()) {
540 return mEditor->isDirty() || mCalSelector->currentCollection().id() != mItem.storageCollectionId();
541 } else {
542 return mEditor->isDirty();
543 }
544}
545
546bool IncidenceDialogPrivate::isValid() const
547{
548 Q_Q(const IncidenceDialog);
549 if (mEditor->isValid()) {
550 // Check if there's a selected collection.
551 if (mCalSelector->currentCollection().isValid()) {
552 return true;
553 } else {
554 qCWarning(INCIDENCEEDITOR_LOG) << "Select a collection first";
555 Q_EMIT q->invalidCollection();
556 }
557 }
558
559 return false;
560}
561
562void IncidenceDialogPrivate::load(const Akonadi::Item &item)
563{
564 Q_Q(IncidenceDialog);
565
566 Q_ASSERT(hasSupportedPayload(item));
567
568 if (CalendarSupport::hasJournal(item)) {
569 // mUi->mTabWidget->removeTab(5);
570 mUi->mTabWidget->removeTab(AttachmentsTab);
571 mUi->mTabWidget->removeTab(RecurrenceTab);
572 mUi->mTabWidget->removeTab(AlarmsTab);
573 mUi->mTabWidget->removeTab(AttendeesTab);
574 mUi->mTabWidget->removeTab(ResourcesTab);
575 }
576
578 mEditor->load(item);
579
581 const QStringList allEmails = IncidenceEditorNG::EditorConfig::instance()->allEmails();
582 const KCalendarCore::Attendee me = incidence->attendeeByMails(allEmails);
583
584 if (incidence->attendeeCount() > 1 // >1 because you won't drink alone
585 && !me.isNull()
588 // Show the invitation bar: "You are invited [accept] [decline]"
589 mUi->mInvitationBar->show();
590 } else {
591 mUi->mInvitationBar->hide();
592 }
593
594 qCDebug(INCIDENCEEDITOR_LOG) << "Loading item " << item.id() << "; parent " << item.parentCollection().id() << "; storage " << item.storageCollectionId();
595
596 if (item.storageCollectionId() > -1) {
598 }
599
600 if (!mCalSelector->mimeTypeFilter().contains(incidence->mimeType())) {
601 mCalSelector->setMimeTypeFilter({incidence->mimeType()});
602 }
603
604 if (mEditor->type() == KCalendarCore::Incidence::TypeTodo) {
605 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")));
606 } else if (mEditor->type() == KCalendarCore::Incidence::TypeEvent) {
607 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-day")));
608 } else if (mEditor->type() == KCalendarCore::Incidence::TypeJournal) {
609 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-pim-journal")));
610 }
611
612 // Initialize tab's titles
613 updateAttachmentCount(incidence->attachments().size());
614 updateResourceCount(mIeResource->resourceCount());
615 updateAttendeeCount(mIeAttendee->attendeeCount());
616 handleRecurrenceChange(mIeRecurrence->currentRecurrenceType());
617 handleAlarmCountChange(incidence->alarms().count());
618
619 mItem = item;
620
621 q->show();
622}
623
624Akonadi::Item IncidenceDialogPrivate::save(const Akonadi::Item &item)
625{
626 Q_ASSERT(mEditor->incidence<KCalendarCore::Incidence>());
627
629 KCalendarCore::Incidence::Ptr newIncidence(incidenceInEditor->clone());
630
631 Akonadi::Item result = item;
632 result.setMimeType(newIncidence->mimeType());
633
634 // There's no editor that has the relatedTo property. We must set it here, by hand.
635 // Otherwise it gets lost.
636 // FIXME: Why don't we clone() incidenceInEditor then pass the clone to save(),
637 // I wonder if we're not leaking other properties.
638 newIncidence->setRelatedTo(incidenceInEditor->relatedTo());
639
640 mEditor->save(newIncidence);
641 mEditor->save(result);
642
643 // Make sure that we don't loose uid for existing incidence
644 newIncidence->setUid(mEditor->incidence<KCalendarCore::Incidence>()->uid());
645
646 // Mark the incidence as changed
647 if (mItem.isValid()) {
648 newIncidence->setRevision(newIncidence->revision() + 1);
649 }
650
651 result.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence);
652 return result;
653}
654
655Akonadi::Collection IncidenceDialogPrivate::selectedCollection() const
656{
657 return mCalSelector->currentCollection();
658}
659
660void IncidenceDialogPrivate::reject(RejectReason reason, const QString &errorMessage)
661{
662 Q_UNUSED(reason)
663
664 Q_Q(IncidenceDialog);
665 qCCritical(INCIDENCEEDITOR_LOG) << "Rejecting:" << errorMessage;
666 q->deleteLater();
667}
668
669/// IncidenceDialog
670
671IncidenceDialog::IncidenceDialog(Akonadi::IncidenceChanger *changer, QWidget *parent, Qt::WindowFlags flags)
672 : QDialog(parent, flags)
673 , d_ptr(new IncidenceDialogPrivate(changer, this))
674{
677
678 d->mUi->mTabWidget->setCurrentIndex(0);
679 d->mUi->mSummaryEdit->setFocus();
680
681 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip", "Save current changes"));
682 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@action:button", "Save changes and close dialog"));
683 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setToolTip(i18nc("@action:button", "Discard changes and close dialog"));
684 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
685
686 auto defaultButton = d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults);
687 defaultButton->setText(i18nc("@action:button", "&Templates..."));
688 defaultButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template")));
689 defaultButton->setToolTip(i18nc("@info:tooltip", "Manage templates for this item"));
690 defaultButton->setWhatsThis(i18nc("@info:whatsthis",
691 "Push this button to show a dialog that helps "
692 "you manage a set of templates. Templates "
693 "can make creating new items easier and faster "
694 "by putting your favorite default values into "
695 "the editor automatically."));
696
697 connect(d->mUi->buttonBox, &QDialogButtonBox::clicked, this, &IncidenceDialog::slotButtonClicked);
698
699 setModal(false);
700
701 connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked, d->mIeAttendee, &IncidenceAttendee::acceptForMe);
702 connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked, d->mUi->mInvitationBar, &QWidget::hide);
703 connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked, d->mIeAttendee, &IncidenceAttendee::declineForMe);
704 connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked, d->mUi->mInvitationBar, &QWidget::hide);
705 connect(this, &IncidenceDialog::invalidCollection, this, [d]() {
706 d->slotInvalidCollection();
707 });
708 readConfig();
709}
710
711IncidenceDialog::~IncidenceDialog()
712{
713 writeConfig();
714}
715
716void IncidenceDialog::writeConfig()
717{
720}
721
722void IncidenceDialog::readConfig()
723{
724 create(); // ensure a window is created
725 windowHandle()->resize(QSize(500, 500));
728 resize(windowHandle()->size()); // workaround for QTBUG-40584
729}
730
731void IncidenceDialog::load(const Akonadi::Item &item, const QDate &activeDate)
732{
734 d->mIeDateTime->setActiveDate(activeDate);
735 if (item.isValid()) { // We're editing
736 d->mItemManager->load(item);
737 } else { // We're creating
738 Q_ASSERT(d->hasSupportedPayload(item));
739 d->load(item);
740 show();
741 }
742}
743
745{
747 d->setCalendarCollection(collection);
748}
749
750void IncidenceDialog::setIsCounterProposal(bool isCounterProposal)
751{
753 d->mItemManager->setIsCounterProposal(isCounterProposal);
754}
755
757{
758 Q_D(const IncidenceDialog);
759 return d->mUi->mSummaryEdit;
760}
761
762void IncidenceDialog::slotButtonClicked(QAbstractButton *button)
763{
765
766 if (d->mUi->buttonBox->button(QDialogButtonBox::Ok) == button) {
767 if (d->isDirty() || d->mInitiallyDirty) {
768 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
769 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
770 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
771 d->mCloseOnSave = true;
772 d->mInitiallyDirty = false;
773 d->mItemManager->save(toItemManagerFlags(d->mUi->mSignItip->isChecked(), d->mUi->mEncryptItip->isChecked()));
774 } else {
775 close();
776 }
777 } else if (d->mUi->buttonBox->button(QDialogButtonBox::Apply) == button) {
778 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
779 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
780 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
781
782 d->mCloseOnSave = false;
783 d->mInitiallyDirty = false;
784 d->mItemManager->save(toItemManagerFlags(d->mUi->mSignItip->isChecked(), d->mUi->mEncryptItip->isChecked()));
785 } else if (d->mUi->buttonBox->button(QDialogButtonBox::Cancel) == button) {
786 if (d->isDirty()
788 i18nc("@info", "Do you really want to cancel?"),
789 i18nc("@title:window", "KOrganizer Confirmation"),
790 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")),
791 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel")))
792 == KMessageBox::ButtonCode::PrimaryAction) {
793 QDialog::reject(); // Discard current changes
794 } else if (!d->isDirty()) {
795 QDialog::reject(); // No pending changes, just close the dialog.
796 } // else { // the user wasn't finished editing after all }
797 } else if (d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
798 d->manageTemplates();
799 } else {
800 Q_ASSERT(false); // Shouldn't happen
801 }
802}
803
804void IncidenceDialog::reject()
805{
807 if (d->isDirty()
809 i18nc("@info", "Do you really want to cancel?"),
810 i18nc("@title:window", "KOrganizer Confirmation"),
811 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")),
812 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel")))
813 == KMessageBox::ButtonCode::PrimaryAction) {
814 QDialog::reject(); // Discard current changes
815 } else if (!d->isDirty()) {
816 QDialog::reject(); // No pending changes, just close the dialog.
817 }
818}
819
820void IncidenceDialog::closeEvent(QCloseEvent *event)
821{
823 if (d->isDirty()
825 i18nc("@info", "Do you really want to cancel?"),
826 i18nc("@title:window", "KOrganizer Confirmation"),
827 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")),
828 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel")))
829 == KMessageBox::ButtonCode::PrimaryAction) {
830 QDialog::reject(); // Discard current changes
832 } else if (!d->isDirty()) {
833 QDialog::reject(); // No pending changes, just close the dialog.
835 } else {
836 event->ignore();
837 }
838}
839
840void IncidenceDialog::setInitiallyDirty(bool initiallyDirty)
841{
843 d->mInitiallyDirty = initiallyDirty;
844}
845
846Akonadi::Item IncidenceDialog::item() const
847{
848 Q_D(const IncidenceDialog);
849 return d->mItemManager->item();
850}
851
852void IncidenceDialog::handleSelectedCollectionChange(const Akonadi::Collection &collection)
853{
855 if (d->mItem.parentCollection().isValid()) {
856 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(collection.id() != d->mItem.parentCollection().id());
857 }
858}
859
860#include "moc_incidencedialog.cpp"
void setMimeTypeFilter(const QStringList &mimetypes)
QStringList mimeTypeFilter() const
Akonadi::Collection currentCollection() const
void currentChanged(const Akonadi::Collection &collection)
void setDefaultCollection(const Collection &collection)
bool isValid() const
void setPayload(const T &p)
Collection & parentCollection()
void setMimeType(const QString &mimeType)
bool hasPayload() const
Id id() const
T payload() const
Collection::Id storageCollectionId() const
bool isValid() const
The CombinedIncidenceEditor combines optional widgets with zero or more IncidenceEditors.
void save(const KCalendarCore::Incidence::Ptr &incidence) override
Store the current values of the editor into.
void load(const KCalendarCore::Incidence::Ptr &incidence) override
Loads all data from.
bool isDirty() const override
Returns whether or not the current values in the editor differ from the initial values or if one of t...
bool isValid() const override
Returns whether or not the content of this editor is valid.
virtual QStringList allEmails() const
Returns all email addresses for the user.
Helper class for creating dialogs that let the user create and edit the payload of Akonadi items (e....
void save(ItipPrivacyFlags itipPrivacy=ItipPrivacyPlain)
Saves the new or modified item.
Akonadi::Item item(ItemState state=AfterSave) const
Returns the last saved item with payload or an invalid item when save is not called yet.
The IncidenceDescriptionEditor keeps track of the following Incidence parts:
The IncidenceDialog class.
virtual void load(const Akonadi::Item &item, const QDate &activeDate=QDate())
Loads the.
virtual void selectCollection(const Akonadi::Collection &collection)
Sets the Collection combobox to.
IncidenceDialog(Akonadi::IncidenceChanger *changer=nullptr, QWidget *parent=nullptr, Qt::WindowFlags flags={})
IncidenceDialog.
QObject * typeAheadReceiver() const
Returns the object that will receive all key events.
void setInitiallyDirty(bool initiallyDirty)
By default, if you load an incidence into the editor ( load(item) ), then press [OK] without changing...
KCalendarCore::IncidenceBase::IncidenceType type() const
Returns the type of the Incidence that is currently loaded.
void dirtyStatusChanged(bool isDirty)
Signals whether the dirty status of this editor has changed.
QSharedPointer< IncidenceT > incidence() const
Convenience method to get a pointer for a specific const Incidence Type.
The IncidenceWhatWhere editor keeps track of the following Incidence parts:
PartStat status() const
static QString createUniqueId()
bool save(const Calendar::Ptr &calendar, const QString &fileName) override
bool load(const Calendar::Ptr &calendar, const QString &fileName) override
QSharedPointer< Incidence > Ptr
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Journal::Ptr journal(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
ButtonCode warningTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
KGuiItem cancel()
KCONFIGGUI_EXPORT void saveWindowSize(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options=KConfigGroup::Normal)
KCONFIGGUI_EXPORT void restoreWindowSize(QWindow *window, const KConfigGroup &config)
void clicked(bool checked)
void setCurrentIndex(int index)
virtual void closeEvent(QCloseEvent *e) override
void setModal(bool modal)
virtual void reject()
void clicked(QAbstractButton *button)
bool mkpath(const QString &dirPath) const const
QFlags< T > & setFlag(Enum flag, bool on)
QIcon fromTheme(const QString &name)
T & first()
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
bool contains(const QSet< T > &other) const const
bool isNull() const const
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
QString writableLocation(StandardLocation type)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
WA_DeleteOnClose
typedef WindowFlags
QTimeZone systemTimeZone()
bool close()
void create(WId window, bool initializeWindow, bool destroyOldWindow)
virtual bool event(QEvent *event) override
void hide()
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void show()
void resize(const QSize &)
QWindow * windowHandle() const const
void resize(const QSize &newSize)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.