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 {
69 GeneralTab = 0,
70 AttendeesTab,
71 ResourcesTab,
72 AlarmsTab,
73 RecurrenceTab,
74 AttachmentsTab
75};
76
77class IncidenceDialogPrivate : public ItemEditorUi
78{
79 IncidenceDialog *q_ptr;
80 Q_DECLARE_PUBLIC(IncidenceDialog)
81
82public:
83 Ui::EventOrTodoDesktop *mUi = nullptr;
84 Akonadi::CollectionComboBox *mCalSelector = nullptr;
85 bool mCloseOnSave = false;
86
87 EditorItemManager *mItemManager = nullptr;
88 CombinedIncidenceEditor *mEditor = nullptr;
89 IncidenceDateTime *mIeDateTime = nullptr;
90 IncidenceAttendee *mIeAttendee = nullptr;
91 IncidenceRecurrence *mIeRecurrence = nullptr;
92 IncidenceResource *mIeResource = nullptr;
93 bool mInitiallyDirty = false;
94 Akonadi::Item mItem;
95 [[nodiscard]] QString typeToString(const int type) const;
96
97public:
98 IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq);
99 ~IncidenceDialogPrivate() override;
100
101 /// General methods
102 void handleAlarmCountChange(int newCount);
103 void handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type);
104 void loadTemplate(const QString &templateName);
105 void manageTemplates();
106 void saveTemplate(const QString &templateName);
107 void storeTemplatesInConfig(const QStringList &newTemplates);
108 void updateAttachmentCount(int newCount);
109 void updateAttendeeCount(int newCount);
110 void updateResourceCount(int newCount);
111 void updateButtonStatus(bool isDirty);
112 void showMessage(const QString &text, KMessageWidget::MessageType type);
113 void slotInvalidCollection();
114 void setCalendarCollection(const Akonadi::Collection &collection);
115
116 /// ItemEditorUi methods
117 [[nodiscard]] bool containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const override;
118 void handleItemSaveFinish(EditorItemManager::SaveAction);
119 void handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage);
120 [[nodiscard]] bool hasSupportedPayload(const Akonadi::Item &item) const override;
121 [[nodiscard]] bool isDirty() const override;
122 [[nodiscard]] bool isValid() const override;
123 void load(const Akonadi::Item &item) override;
124 Akonadi::Item save(const Akonadi::Item &item) override;
125 [[nodiscard]] Akonadi::Collection selectedCollection() const override;
126
127 void reject(RejectReason reason, const QString &errorMessage = QString()) override;
128};
129}
130
131IncidenceDialogPrivate::IncidenceDialogPrivate(Akonadi::IncidenceChanger *changer, IncidenceDialog *qq)
132 : q_ptr(qq)
133 , mUi(new Ui::EventOrTodoDesktop)
134 , mCalSelector(new Akonadi::CollectionComboBox(changer ? changer->entityTreeModel() : nullptr))
135 , mItemManager(new EditorItemManager(this, changer))
136 , mEditor(new CombinedIncidenceEditor(qq))
137{
138 Q_Q(IncidenceDialog);
139 mUi->setupUi(q);
140 mUi->mMessageWidget->hide();
141 auto layout = new QGridLayout(mUi->mCalSelectorPlaceHolder);
142 layout->setSpacing(0);
143 layout->setContentsMargins(0, 0, 0, 0);
144 layout->addWidget(mCalSelector);
145 mCalSelector->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
146 mUi->label->setBuddy(mCalSelector);
147 q->connect(mCalSelector, &Akonadi::CollectionComboBox::currentChanged, q, &IncidenceDialog::handleSelectedCollectionChange);
148
149 // Now instantiate the logic of the dialog. These editors update the ui, validate
150 // fields and load/store incidences in the ui.
151 auto ieGeneral = new IncidenceWhatWhere(mUi);
152 mEditor->combine(ieGeneral);
153
154 auto ieCategories = new IncidenceCategories(mUi);
155 mEditor->combine(ieCategories);
156
157 mIeDateTime = new IncidenceDateTime(mUi);
158 mEditor->combine(mIeDateTime);
159
160 auto ieCompletionPriority = new IncidenceCompletionPriority(mUi);
161 mEditor->combine(ieCompletionPriority);
162
163 auto ieDescription = new IncidenceDescription(mUi);
164 mEditor->combine(ieDescription);
165
166 auto ieAlarm = new IncidenceAlarm(mIeDateTime, mUi);
167 mEditor->combine(ieAlarm);
168
169 auto ieAttachments = new IncidenceAttachment(mUi);
170 mEditor->combine(ieAttachments);
171
172 mIeRecurrence = new IncidenceRecurrence(mIeDateTime, mUi);
173 mEditor->combine(mIeRecurrence);
174
175 auto ieSecrecy = new IncidenceSecrecy(mUi);
176 mEditor->combine(ieSecrecy);
177
178 mIeAttendee = new IncidenceAttendee(qq, mIeDateTime, mUi);
179 mIeAttendee->setParent(qq);
180 mEditor->combine(mIeAttendee);
181
182 mIeResource = new IncidenceResource(mIeAttendee, mIeDateTime, mUi);
183 mEditor->combine(mIeResource);
184
185 // Set the default collection
186 const qint64 colId = CalendarSupport::KCalPrefs::instance()->defaultCalendarId();
187 const Akonadi::Collection col(colId);
188 setCalendarCollection(col);
189
190 q->connect(mEditor, &CombinedIncidenceEditor::showMessage, q, [this](const QString &reason, KMessageWidget::MessageType msgType) {
191 showMessage(reason, msgType);
192 });
193 q->connect(mEditor, &IncidenceEditor::dirtyStatusChanged, q, [this](bool isDirty) {
194 updateButtonStatus(isDirty);
195 });
196 q->connect(mItemManager, &EditorItemManager::itemSaveFinished, q, [this](EditorItemManager::SaveAction action) {
197 handleItemSaveFinish(action);
198 });
199 q->connect(mItemManager, &EditorItemManager::itemSaveFailed, q, [this](EditorItemManager::SaveAction action, const QString &message) {
200 handleItemSaveFail(action, message);
201 });
202 q->connect(ieAlarm, &IncidenceAlarm::alarmCountChanged, q, [this](int newCount) {
203 handleAlarmCountChange(newCount);
204 });
205 q->connect(mIeRecurrence, &IncidenceRecurrence::recurrenceChanged, q, [this](IncidenceEditorNG::RecurrenceType type) {
206 handleRecurrenceChange(type);
207 });
208 q->connect(ieAttachments, &IncidenceAttachment::attachmentCountChanged, q, [this](int newCount) {
209 updateAttachmentCount(newCount);
210 });
211 q->connect(mIeAttendee, &IncidenceAttendee::attendeeCountChanged, q, [this](int count) {
212 updateAttendeeCount(count);
213 });
214 q->connect(mIeResource, &IncidenceResource::resourceCountChanged, q, [this](int count) {
215 updateResourceCount(count);
216 });
217}
218
219IncidenceDialogPrivate::~IncidenceDialogPrivate()
220{
221 delete mItemManager;
222 delete mEditor;
223 delete mUi;
224}
225
226void IncidenceDialogPrivate::slotInvalidCollection()
227{
228 showMessage(i18n("Select a valid collection first."), KMessageWidget::Warning);
229}
230
231void IncidenceDialogPrivate::setCalendarCollection(const Akonadi::Collection &collection)
232{
233 if (collection.isValid()) {
234 mCalSelector->setDefaultCollection(collection);
235 } else {
236 mCalSelector->setCurrentIndex(0);
237 }
238}
239
240void IncidenceDialogPrivate::showMessage(const QString &text, KMessageWidget::MessageType type)
241{
242 mUi->mMessageWidget->setText(text);
243 mUi->mMessageWidget->setMessageType(type);
244 mUi->mMessageWidget->show();
245}
246
247void IncidenceDialogPrivate::handleAlarmCountChange(int newCount)
248{
249 QString tabText;
250 if (newCount > 0) {
251 tabText = i18nc("@title:tab Tab to configure the reminders of an event or todo", "Reminder (%1)", newCount);
252 } else {
253 tabText = i18nc("@title:tab Tab to configure the reminders of an event or todo", "Reminder");
254 }
255
256 mUi->mTabWidget->setTabText(AlarmsTab, tabText);
257}
258
259void IncidenceDialogPrivate::handleRecurrenceChange(IncidenceEditorNG::RecurrenceType type)
260{
261 QString tabText = i18nc("@title:tab Tab to configure the recurrence of an event or todo", "Rec&urrence");
262
263 // Keep this numbers in sync with the items in mUi->mRecurrenceTypeCombo. I
264 // tried adding an enum to IncidenceRecurrence but for whatever reason I could
265 // Qt not play nice with namespaced enums in signal/slot connections.
266 // Anyways, I don't expect these values to change.
267 switch (type) {
268 case RecurrenceTypeNone:
269 break;
270 case RecurrenceTypeDaily:
271 tabText += i18nc("@title:tab Daily recurring event, capital first letter only", " (D)");
272 break;
273 case RecurrenceTypeWeekly:
274 tabText += i18nc("@title:tab Weekly recurring event, capital first letter only", " (W)");
275 break;
276 case RecurrenceTypeMonthly:
277 tabText += i18nc("@title:tab Monthly recurring event, capital first letter only", " (M)");
278 break;
279 case RecurrenceTypeYearly:
280 tabText += i18nc("@title:tab Yearly recurring event, capital first letter only", " (Y)");
281 break;
282 case RecurrenceTypeException:
283 tabText += i18nc("@title:tab Exception to a recurring event, capital first letter only", " (E)");
284 break;
285 default:
286 Q_ASSERT_X(false, "handleRecurrenceChange", "Fix your program");
287 }
288
289 mUi->mTabWidget->setTabText(RecurrenceTab, tabText);
290}
291
292QString IncidenceDialogPrivate::typeToString(const int type) const
293{
294 // Do not translate.
295 switch (type) {
297 return QStringLiteral("Event");
299 return QStringLiteral("Todo");
301 return QStringLiteral("Journal");
302 default:
303 return QStringLiteral("Unknown");
304 }
305}
306
307void IncidenceDialogPrivate::loadTemplate(const QString &templateName)
308{
309 Q_Q(IncidenceDialog);
310
312
314 QStringLiteral("/korganizer/templates/") + typeToString(mEditor->type()) + QLatin1Char('/') + templateName);
315
316 if (fileName.isEmpty()) {
317 KMessageBox::error(q, i18nc("@info", "Unable to find template '%1'.", templateName));
318 return;
319 }
320
322 if (!format.load(cal, fileName)) {
323 KMessageBox::error(q, i18nc("@info", "Error loading template file '%1'.", fileName));
324 return;
325 }
326
327 KCalendarCore::Incidence::List incidences = cal->incidences();
328 if (incidences.isEmpty()) {
329 KMessageBox::error(q, i18nc("@info", "Template does not contain a valid incidence."));
330 return;
331 }
332
333 mIeDateTime->setActiveDate(QDate());
336
337 // We add a custom property so that some fields aren't loaded, dates for example
338 newInc->setCustomProperty(QByteArray("kdepim"), "isTemplate", QStringLiteral("true"));
339 mEditor->load(newInc);
340 newInc->removeCustomProperty(QByteArray(), "isTemplate");
341}
342
343void IncidenceDialogPrivate::manageTemplates()
344{
345 Q_Q(IncidenceDialog);
346
347 QStringList &templates = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type());
348
350 new IncidenceEditorNG::TemplateManagementDialog(q, templates, KCalUtils::Stringify::incidenceType(mEditor->type())));
351
352 q->connect(dialog, &TemplateManagementDialog::loadTemplate, q, [this](const QString &templateName) {
353 loadTemplate(templateName);
354 });
355 q->connect(dialog, &TemplateManagementDialog::templatesChanged, q, [this](const QStringList &templates) {
356 storeTemplatesInConfig(templates);
357 });
358 q->connect(dialog, &TemplateManagementDialog::saveTemplate, q, [this](const QString &templateName) {
359 saveTemplate(templateName);
360 });
361 dialog->exec();
362 delete dialog;
363}
364
365void IncidenceDialogPrivate::saveTemplate(const QString &templateName)
366{
367 Q_ASSERT(!templateName.isEmpty());
368
370
371 switch (mEditor->type()) {
374 mEditor->save(event);
375 cal->addEvent(KCalendarCore::Event::Ptr(event->clone()));
376 break;
377 }
380 mEditor->save(todo);
381 cal->addTodo(KCalendarCore::Todo::Ptr(todo->clone()));
382 break;
383 }
386 mEditor->save(journal);
387 cal->addJournal(KCalendarCore::Journal::Ptr(journal->clone()));
388 break;
389 }
390 default:
391 Q_ASSERT_X(false, "saveTemplate", "Fix your program");
392 }
393
394 QString fileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/korganizer/templates/")
395 + typeToString(mEditor->type()) + QLatin1Char('/');
396 QDir().mkpath(fileName);
397 fileName += templateName;
398
400 format.save(cal, fileName);
401}
402
403void IncidenceDialogPrivate::storeTemplatesInConfig(const QStringList &templateNames)
404{
405 // I find this somewhat broken. templates() returns a reference, maybe it should
406 // be changed by adding a setTemplates method.
407 const QStringList origTemplates = IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type());
408 const QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("korganizer/templates/")
409 + typeToString(mEditor->type()) + QLatin1Char('/');
410 QDir().mkpath(defaultPath);
411 for (const QString &tmpl : origTemplates) {
412 if (!templateNames.contains(tmpl)) {
413 const QString fileName = defaultPath + tmpl;
414 QFile file(fileName);
415 if (file.exists()) {
416 file.remove();
417 }
418 }
419 }
420
421 IncidenceEditorNG::EditorConfig::instance()->templates(mEditor->type()) = templateNames;
422 IncidenceEditorNG::EditorConfig::instance()->config()->save();
423}
424
425void IncidenceDialogPrivate::updateAttachmentCount(int newCount)
426{
427 if (newCount > 0) {
428 mUi->mTabWidget->setTabText(AttachmentsTab, i18nc("@title:tab Tab to modify attachments of an event or todo", "Attac&hments (%1)", newCount));
429 } else {
430 mUi->mTabWidget->setTabText(AttachmentsTab, i18nc("@title:tab Tab to modify attachments of an event or todo", "Attac&hments"));
431 }
432}
433
434void IncidenceDialogPrivate::updateAttendeeCount(int newCount)
435{
436 if (newCount > 0) {
437 mUi->mTabWidget->setTabText(AttendeesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Attendees (%1)", newCount));
438 } else {
439 mUi->mTabWidget->setTabText(AttendeesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Attendees"));
440 }
441}
442
443void IncidenceDialogPrivate::updateResourceCount(int newCount)
444{
445 if (newCount > 0) {
446 mUi->mTabWidget->setTabText(ResourcesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Resources (%1)", newCount));
447 } else {
448 mUi->mTabWidget->setTabText(ResourcesTab, i18nc("@title:tab Tab to modify attendees of an event or todo", "&Resources"));
449 }
450}
451
452void IncidenceDialogPrivate::updateButtonStatus(bool isDirty)
453{
454 mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty || mInitiallyDirty);
455}
456
457bool IncidenceDialogPrivate::containsPayloadIdentifiers(const QSet<QByteArray> &partIdentifiers) const
458{
459 return partIdentifiers.contains(QByteArray("PLD:RFC822"));
460}
461
462void IncidenceDialogPrivate::handleItemSaveFail(EditorItemManager::SaveAction, const QString &errorMessage)
463{
464 Q_Q(IncidenceDialog);
465
466 bool retry = false;
467
468 if (!errorMessage.isEmpty()) {
469 const QString message = i18nc("@info",
470 "Unable to store the incidence in the calendar. Try again?\n\n "
471 "Reason: %1",
472 errorMessage);
473 const int answer = KMessageBox::warningTwoActions(q,
474 message,
475 QString(),
476 KGuiItem(i18nc("@action:button", "Retry"), QStringLiteral("dialog-ok")),
478 retry = (answer == KMessageBox::ButtonCode::PrimaryAction);
479 }
480
481 if (retry) {
482 mItemManager->save();
483 } else {
484 updateButtonStatus(isDirty());
485 mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
486 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
487 }
488}
489
490void IncidenceDialogPrivate::handleItemSaveFinish(EditorItemManager::SaveAction saveAction)
491{
492 Q_Q(IncidenceDialog);
493
494 if ((mEditor->type() == KCalendarCore::Incidence::TypeEvent) && (mCalSelector->count() > 1)
495 && (CalendarSupport::KCalPrefs::instance()->defaultCalendarId() == -1)) {
496 const QString collectionName = mCalSelector->currentText();
497 const QString message = xi18nc("@info",
498 "<para>You have not set a default calendar for your events yet.</para>"
499 "<para>Setting a default calendar will make creating new events faster and "
500 "easier with less chance of filing them into the wrong folder.</para>"
501 "<para>Would you like to set your default events calendar to "
502 "<resource>%1</resource>?</para>",
503 collectionName);
504 const int answer = KMessageBox::questionTwoActions(q,
505 message,
506 i18nc("@title:window", "Set Default Calendar?"),
507 KGuiItem(i18nc("@action:button", "Set As Default"), QStringLiteral("dialog-ok")),
508 KGuiItem(i18nc("@action:button", "Do Not Set"), QStringLiteral("dialog-cancel")),
509 QStringLiteral("setDefaultCalendarCollection"));
510 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
511 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId(mItem.storageCollectionId());
512 }
513 }
514
515 if (mCloseOnSave) {
516 q->accept();
517 } else {
518 const Akonadi::Item item = mItemManager->item();
519 Q_ASSERT(item.isValid());
520 Q_ASSERT(item.hasPayload());
522 // Now the item is successfully saved, reload it in the editor in order to
523 // reset the dirty status of the editor.
525 mEditor->load(item);
526
527 // Set the buttons to a reasonable state as well (ok and apply should be
528 // disabled at this point).
529 mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
530 mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
531 mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(isDirty());
532 }
533
534 if (saveAction == EditorItemManager::Create) {
535 Q_EMIT q->incidenceCreated(mItemManager->item());
536 }
537}
538
539bool IncidenceDialogPrivate::hasSupportedPayload(const Akonadi::Item &item) const
540{
542}
543
544bool IncidenceDialogPrivate::isDirty() const
545{
546 if (mItem.isValid()) {
547 return mEditor->isDirty() || mCalSelector->currentCollection().id() != mItem.storageCollectionId();
548 } else {
549 return mEditor->isDirty();
550 }
551}
552
553bool IncidenceDialogPrivate::isValid() const
554{
555 Q_Q(const IncidenceDialog);
556 if (mEditor->isValid()) {
557 // Check if there's a selected collection.
558 if (mCalSelector->currentCollection().isValid()) {
559 return true;
560 } else {
561 qCWarning(INCIDENCEEDITOR_LOG) << "Select a collection first";
562 Q_EMIT q->invalidCollection();
563 }
564 }
565
566 return false;
567}
568
569void IncidenceDialogPrivate::load(const Akonadi::Item &item)
570{
571 Q_Q(IncidenceDialog);
572
573 Q_ASSERT(hasSupportedPayload(item));
574
575 if (CalendarSupport::hasJournal(item)) {
576 // mUi->mTabWidget->removeTab(5);
577 mUi->mTabWidget->removeTab(AttachmentsTab);
578 mUi->mTabWidget->removeTab(RecurrenceTab);
579 mUi->mTabWidget->removeTab(AlarmsTab);
580 mUi->mTabWidget->removeTab(AttendeesTab);
581 mUi->mTabWidget->removeTab(ResourcesTab);
582 }
583
585 mEditor->load(item);
586
588 const QStringList allEmails = IncidenceEditorNG::EditorConfig::instance()->allEmails();
589 const KCalendarCore::Attendee me = incidence->attendeeByMails(allEmails);
590
591 if (incidence->attendeeCount() > 1 // >1 because you won't drink alone
592 && !me.isNull()
595 // Show the invitation bar: "You are invited [accept] [decline]"
596 mUi->mInvitationBar->show();
597 } else {
598 mUi->mInvitationBar->hide();
599 }
600
601 qCDebug(INCIDENCEEDITOR_LOG) << "Loading item " << item.id() << "; parent " << item.parentCollection().id() << "; storage " << item.storageCollectionId();
602
603 if (item.storageCollectionId() > -1) {
605 }
606
607 if (!mCalSelector->mimeTypeFilter().contains(incidence->mimeType())) {
608 mCalSelector->setMimeTypeFilter({incidence->mimeType()});
609 }
610
611 if (mEditor->type() == KCalendarCore::Incidence::TypeTodo) {
612 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")));
613 } else if (mEditor->type() == KCalendarCore::Incidence::TypeEvent) {
614 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-calendar-day")));
615 } else if (mEditor->type() == KCalendarCore::Incidence::TypeJournal) {
616 q->setWindowIcon(QIcon::fromTheme(QStringLiteral("view-pim-journal")));
617 }
618
619 // Initialize tab's titles
620 updateAttachmentCount(incidence->attachments().size());
621 updateResourceCount(mIeResource->resourceCount());
622 updateAttendeeCount(mIeAttendee->attendeeCount());
623 handleRecurrenceChange(mIeRecurrence->currentRecurrenceType());
624 handleAlarmCountChange(incidence->alarms().count());
625
626 mItem = item;
627
628 q->show();
629}
630
631Akonadi::Item IncidenceDialogPrivate::save(const Akonadi::Item &item)
632{
633 Q_ASSERT(mEditor->incidence<KCalendarCore::Incidence>());
634
636 KCalendarCore::Incidence::Ptr newIncidence(incidenceInEditor->clone());
637
638 Akonadi::Item result = item;
639 result.setMimeType(newIncidence->mimeType());
640
641 // There's no editor that has the relatedTo property. We must set it here, by hand.
642 // Otherwise it gets lost.
643 // FIXME: Why don't we clone() incidenceInEditor then pass the clone to save(),
644 // I wonder if we're not leaking other properties.
645 newIncidence->setRelatedTo(incidenceInEditor->relatedTo());
646
647 mEditor->save(newIncidence);
648 mEditor->save(result);
649
650 // Make sure that we don't loose uid for existing incidence
651 newIncidence->setUid(mEditor->incidence<KCalendarCore::Incidence>()->uid());
652
653 // Mark the incidence as changed
654 if (mItem.isValid()) {
655 newIncidence->setRevision(newIncidence->revision() + 1);
656 }
657
658 result.setPayload<KCalendarCore::Incidence::Ptr>(newIncidence);
659 return result;
660}
661
662Akonadi::Collection IncidenceDialogPrivate::selectedCollection() const
663{
664 return mCalSelector->currentCollection();
665}
666
667void IncidenceDialogPrivate::reject(RejectReason reason, const QString &errorMessage)
668{
669 Q_UNUSED(reason)
670
671 Q_Q(IncidenceDialog);
672 qCCritical(INCIDENCEEDITOR_LOG) << "Rejecting:" << errorMessage;
673 q->deleteLater();
674}
675
676/// IncidenceDialog
677
678IncidenceDialog::IncidenceDialog(Akonadi::IncidenceChanger *changer, QWidget *parent, Qt::WindowFlags flags)
679 : QDialog(parent, flags)
680 , d_ptr(new IncidenceDialogPrivate(changer, this))
681{
684
685 d->mUi->mTabWidget->setCurrentIndex(0);
686 d->mUi->mSummaryEdit->setFocus();
687
688 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip", "Save current changes"));
689 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@action:button", "Save changes and close dialog"));
690 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setToolTip(i18nc("@action:button", "Discard changes and close dialog"));
691 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
692
693 auto defaultButton = d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults);
694 defaultButton->setText(i18nc("@action:button", "&Templates…"));
695 defaultButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template")));
696 defaultButton->setToolTip(i18nc("@info:tooltip", "Manage templates for this item"));
697 defaultButton->setWhatsThis(i18nc("@info:whatsthis",
698 "Push this button to show a dialog that helps "
699 "you manage a set of templates. Templates "
700 "can make creating new items easier and faster "
701 "by putting your favorite default values into "
702 "the editor automatically."));
703
704 connect(d->mUi->buttonBox, &QDialogButtonBox::clicked, this, &IncidenceDialog::slotButtonClicked);
705
706 setModal(false);
707
708 connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked, d->mIeAttendee, &IncidenceAttendee::acceptForMe);
709 connect(d->mUi->mAcceptInvitationButton, &QAbstractButton::clicked, d->mUi->mInvitationBar, &QWidget::hide);
710 connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked, d->mIeAttendee, &IncidenceAttendee::declineForMe);
711 connect(d->mUi->mDeclineInvitationButton, &QAbstractButton::clicked, d->mUi->mInvitationBar, &QWidget::hide);
712 connect(this, &IncidenceDialog::invalidCollection, this, [d]() {
713 d->slotInvalidCollection();
714 });
715 readConfig();
716}
717
718IncidenceDialog::~IncidenceDialog()
719{
720 writeConfig();
721}
722
723void IncidenceDialog::writeConfig()
724{
725 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(myIncidenceDialogConfigGroupName));
727}
728
729void IncidenceDialog::readConfig()
730{
731 create(); // ensure a window is created
732 windowHandle()->resize(QSize(500, 500));
733 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(myIncidenceDialogConfigGroupName));
735 resize(windowHandle()->size()); // workaround for QTBUG-40584
736}
737
738void IncidenceDialog::load(const Akonadi::Item &item, const QDate &activeDate)
739{
741 d->mIeDateTime->setActiveDate(activeDate);
742 if (item.isValid()) { // We're editing
743 d->mItemManager->load(item);
744 } else { // We're creating
745 Q_ASSERT(d->hasSupportedPayload(item));
746 d->load(item);
747 show();
748 }
749}
750
752{
754 d->setCalendarCollection(collection);
755}
756
757void IncidenceDialog::setIsCounterProposal(bool isCounterProposal)
758{
760 d->mItemManager->setIsCounterProposal(isCounterProposal);
761}
762
764{
765 Q_D(const IncidenceDialog);
766 return d->mUi->mSummaryEdit;
767}
768
769void IncidenceDialog::slotButtonClicked(QAbstractButton *button)
770{
772
773 if (d->mUi->buttonBox->button(QDialogButtonBox::Ok) == button) {
774 if (d->isDirty() || d->mInitiallyDirty) {
775 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
776 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
777 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
778 d->mCloseOnSave = true;
779 d->mInitiallyDirty = false;
780 d->mItemManager->save(toItemManagerFlags(d->mUi->mSignItip->isChecked(), d->mUi->mEncryptItip->isChecked()));
781 } else {
782 close();
783 }
784 } else if (d->mUi->buttonBox->button(QDialogButtonBox::Apply) == button) {
785 d->mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
786 d->mUi->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
787 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
788
789 d->mCloseOnSave = false;
790 d->mInitiallyDirty = false;
791 d->mItemManager->save(toItemManagerFlags(d->mUi->mSignItip->isChecked(), d->mUi->mEncryptItip->isChecked()));
792 } else if (d->mUi->buttonBox->button(QDialogButtonBox::Cancel) == button) {
793 if (d->isDirty()
795 i18nc("@info", "Do you really want to cancel?"),
796 i18nc("@title:window", "KOrganizer Confirmation"),
797 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")),
798 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel")))
799 == KMessageBox::ButtonCode::PrimaryAction) {
800 QDialog::reject(); // Discard current changes
801 } else if (!d->isDirty()) {
802 QDialog::reject(); // No pending changes, just close the dialog.
803 } // else { // the user wasn't finished editing after all }
804 } else if (d->mUi->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
805 d->manageTemplates();
806 } else {
807 Q_ASSERT(false); // Shouldn't happen
808 }
809}
810
811void IncidenceDialog::reject()
812{
814 if (d->isDirty()
816 i18nc("@info", "Do you really want to cancel?"),
817 i18nc("@title:window", "KOrganizer Confirmation"),
818 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")),
819 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel")))
820 == KMessageBox::ButtonCode::PrimaryAction) {
821 QDialog::reject(); // Discard current changes
822 } else if (!d->isDirty()) {
823 QDialog::reject(); // No pending changes, just close the dialog.
824 }
825}
826
827void IncidenceDialog::closeEvent(QCloseEvent *event)
828{
830 if (d->isDirty()
832 i18nc("@info", "Do you really want to cancel?"),
833 i18nc("@title:window", "KOrganizer Confirmation"),
834 KGuiItem(i18nc("@action:button", "Cancel Editing"), QStringLiteral("dialog-ok")),
835 KGuiItem(i18nc("@action:button", "Do Not Cancel"), QStringLiteral("dialog-cancel")))
836 == KMessageBox::ButtonCode::PrimaryAction) {
837 QDialog::reject(); // Discard current changes
839 } else if (!d->isDirty()) {
840 QDialog::reject(); // No pending changes, just close the dialog.
842 } else {
843 event->ignore();
844 }
845}
846
847void IncidenceDialog::setInitiallyDirty(bool initiallyDirty)
848{
850 d->mInitiallyDirty = initiallyDirty;
851}
852
853Akonadi::Item IncidenceDialog::item() const
854{
855 Q_D(const IncidenceDialog);
856 return d->mItemManager->item();
857}
858
859void IncidenceDialog::handleSelectedCollectionChange(const Akonadi::Collection &collection)
860{
862 if (d->mItem.parentCollection().isValid()) {
863 d->mUi->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(collection.id() != d->mItem.parentCollection().id());
864 }
865}
866
867#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)
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 Fri Dec 13 2024 11:52:53 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.