1 /*
2 This file is part of KOrganizer.
3
4 SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <schumacher@kde.org>
5 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
8 */
9
10 #include "koeventpopupmenu.h"
11 #include "korganizer_debug.h"
12
13 #include <Akonadi/ItemCreateJob>
14 #include <Akonadi/Notes/NoteUtils>
15
16 #include <CalendarSupport/CalPrinter>
17 #include <CalendarSupport/NoteEditDialog>
18 #include <CalendarSupport/Utils>
19
20 #include <KCalendarCore/CalFormat>
21
22 #include <IncidenceEditor/IncidenceDialog>
23 #include <IncidenceEditor/IncidenceDialogFactory>
24
25 #include <QCoreApplication>
26
KOEventPopupMenu(const Akonadi::ETMCalendar::Ptr & calendar,QWidget * parent)27 KOEventPopupMenu::KOEventPopupMenu(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent)
28 : QMenu(parent)
29 {
30 init(calendar, MenuStyle::NormalView);
31 }
32
KOEventPopupMenu(const Akonadi::ETMCalendar::Ptr & calendar,MenuStyle menuStyle,QWidget * parent)33 KOEventPopupMenu::KOEventPopupMenu(const Akonadi::ETMCalendar::Ptr &calendar, MenuStyle menuStyle, QWidget *parent)
34 : QMenu(parent)
35 {
36 init(calendar, menuStyle);
37 }
38
init(const Akonadi::ETMCalendar::Ptr & calendar,MenuStyle menuStyle)39 void KOEventPopupMenu::init(const Akonadi::ETMCalendar::Ptr &calendar, MenuStyle menuStyle)
40 {
41 mCalendar = calendar;
42
43 // These actions are always shown, no matter what
44 addAction(QIcon::fromTheme(QStringLiteral("document-preview")), i18nc("@action:inmenu", "&Show"), this, &KOEventPopupMenu::popupShow);
45
46 addAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18nc("@action:inmenu", "&Edit..."), this, &KOEventPopupMenu::popupEdit);
47
48 addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu delete this incidence", "&Delete"), this, &KOEventPopupMenu::popupDelete);
49
50 addSeparator();
51
52 addAction(QIcon::fromTheme(QStringLiteral("document-print")), i18nc("@action:inmenu", "&Print..."), this, &KOEventPopupMenu::slotPrint);
53
54 addAction(QIcon::fromTheme(QStringLiteral("document-print-preview")), i18nc("@action:inmenu", "Print Previe&w..."), this, &KOEventPopupMenu::printPreview);
55
56 // Add more menu actions according to Menu style
57 switch (menuStyle) {
58 case MenuStyle::NormalView:
59 appendEditOnlyItems();
60 appendEventOnlyItems();
61 appendTodoOnlyItems();
62 appendReminderOnlyItems();
63 appendRecurrenceOnlyItems();
64 appendShareOnlyItems();
65 default:
66 break;
67 }
68 }
69
appendEditOnlyItems()70 void KOEventPopupMenu::appendEditOnlyItems()
71 {
72 mEditOnlyItems.append(addSeparator());
73
74 mEditOnlyItems.append(
75 addAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18nc("@action:inmenu cut this incidence", "C&ut"), this, &KOEventPopupMenu::popupCut));
76 mEditOnlyItems.append(
77 addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu copy this incidence", "&Copy"), this, &KOEventPopupMenu::popupCopy));
78 mEditOnlyItems.append(addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18nc("@action:inmenu", "&Paste"), this, &KOEventPopupMenu::popupPaste));
79 }
80
appendEventOnlyItems()81 void KOEventPopupMenu::appendEventOnlyItems()
82 {
83 mEventOnlyItems.append(addSeparator());
84
85 mEventOnlyItems.append(
86 addAction(QIcon::fromTheme(QStringLiteral("task-new")), i18nc("@action:inmenu", "Create To-do from Event"), this, &KOEventPopupMenu::createTodo));
87 mEventOnlyItems.last()->setObjectName(QStringLiteral("createtodo")); // id used by unit test
88
89 mEventOnlyItems.append(addAction(QIcon::fromTheme(QStringLiteral("view-pim-notes")),
90 i18nc("@action:inmenu", "Create Note for Event"),
91 this,
92 qOverload<>(&KOEventPopupMenu::createNote)));
93 mEventOnlyItems.last()->setObjectName(QStringLiteral("createnoteforevent")); // id used by unit test
94 }
95
appendTodoOnlyItems()96 void KOEventPopupMenu::appendTodoOnlyItems()
97 {
98 mTodoOnlyItems.append(addSeparator());
99
100 mTodoOnlyItems.append(addAction(QIcon::fromTheme(QStringLiteral("task-complete")),
101 i18nc("@action:inmenu", "Togg&le To-do Completed"),
102 this,
103 &KOEventPopupMenu::toggleTodoCompleted));
104
105 mTodoOnlyItems.append(addAction(QIcon::fromTheme(QStringLiteral("appointment-new")),
106 i18nc("@action:inmenu", "Create Event from To-do"),
107 this,
108 qOverload<>(&KOEventPopupMenu::createEvent)));
109 mTodoOnlyItems.last()->setObjectName(QStringLiteral("createevent")); // id used by unit test
110
111 mTodoOnlyItems.append(addAction(QIcon::fromTheme(QStringLiteral("view-pim-notes")),
112 i18nc("@action:inmenu", "Create Note for To-do"),
113 this,
114 qOverload<>(&KOEventPopupMenu::createNote)));
115 mTodoOnlyItems.last()->setObjectName(QStringLiteral("createnotefortodo")); // id used by unit test
116 }
117
appendReminderOnlyItems()118 void KOEventPopupMenu::appendReminderOnlyItems()
119 {
120 mReminderOnlyItems.append(addSeparator());
121
122 mToggleReminder =
123 addAction(QIcon::fromTheme(QStringLiteral("appointment-reminder")), i18nc("@action:inmenu", "&Toggle Reminder"), this, &KOEventPopupMenu::toggleAlarm);
124 mReminderOnlyItems.append(mToggleReminder);
125 }
126
appendRecurrenceOnlyItems()127 void KOEventPopupMenu::appendRecurrenceOnlyItems()
128 {
129 mRecurrenceOnlyItems.append(addSeparator());
130 mDissociateOccurrences = addAction(i18nc("@action:inmenu", "&Dissociate From Recurrence..."), this, &KOEventPopupMenu::dissociateOccurrences);
131 mRecurrenceOnlyItems.append(mDissociateOccurrences);
132 }
133
appendShareOnlyItems()134 void KOEventPopupMenu::appendShareOnlyItems()
135 {
136 mShareOnlyItems.append(addSeparator());
137 mShareOnlyItems.append(
138 addAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu", "Send as iCalendar..."), this, &KOEventPopupMenu::forward));
139 }
140
showIncidencePopup(const Akonadi::Item & item,const QDate & qd)141 void KOEventPopupMenu::showIncidencePopup(const Akonadi::Item &item, const QDate &qd)
142 {
143 mCurrentIncidence = item;
144 mCurrentDate = qd;
145
146 if (!CalendarSupport::hasIncidence(mCurrentIncidence) /*&& qd.isValid()*/) {
147 qCDebug(KORGANIZER_LOG) << "No event selected";
148 return;
149 }
150
151 if (!mCalendar) {
152 // TODO fix it
153 qCDebug(KORGANIZER_LOG) << "Calendar is unset";
154 return;
155 }
156
157 KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(mCurrentIncidence);
158 Q_ASSERT(incidence);
159
160 // Determine if this Incidence's calendar is writeable.
161 // Else all actions that might modify the Incidence are disabled.
162 const bool hasChangeRights = mCalendar->hasRight(mCurrentIncidence, Akonadi::Collection::CanChangeItem);
163
164 QList<QAction *>::Iterator it;
165 QList<QAction *>::Iterator end;
166
167 // Enable/Disabled menu edit items
168 end = mEditOnlyItems.end();
169 for (it = mEditOnlyItems.begin(); it != end; ++it) {
170 (*it)->setEnabled(hasChangeRights);
171 }
172
173 // Enable/Disable menu items valid for Events only
174 end = mEventOnlyItems.end();
175 for (it = mEventOnlyItems.begin(); it != end; ++it) {
176 (*it)->setVisible(incidence->type() == KCalendarCore::Incidence::TypeEvent);
177 (*it)->setEnabled(true);
178 }
179
180 // Enable/Disable menu items valid for Todos only
181 end = mTodoOnlyItems.end();
182 for (it = mTodoOnlyItems.begin(); it != end; ++it) {
183 (*it)->setVisible(incidence->type() == KCalendarCore::Incidence::TypeTodo);
184 (*it)->setEnabled(true);
185 }
186 if (mToggleReminder) {
187 mToggleReminder->setText(incidence->hasEnabledAlarms() ? i18nc("@action:inmenu", "&Toggle Reminder Off")
188 : i18nc("@action:inmenu", "&Toggle Reminder On"));
189 }
190
191 // Enable/Disable menu items valid for reminder Incidences only
192 end = mReminderOnlyItems.end();
193 for (it = mReminderOnlyItems.begin(); it != end; ++it) {
194 (*it)->setVisible(incidence->type() != KCalendarCore::Incidence::TypeJournal);
195 (*it)->setEnabled(hasChangeRights);
196 }
197
198 // Enable/Disable menu items valid for recurrent Incidences only
199 end = mRecurrenceOnlyItems.end();
200 for (it = mRecurrenceOnlyItems.begin(); it != end; ++it) {
201 (*it)->setVisible(incidence->recurs());
202 (*it)->setEnabled(hasChangeRights);
203 }
204 if (incidence->recurs()) {
205 const QDateTime thisDateTime(qd, {}, Qt::LocalTime);
206 const bool isLastOccurrence = !incidence->recurrence()->getNextDateTime(thisDateTime).isValid();
207 const bool isFirstOccurrence = !incidence->recurrence()->getPreviousDateTime(thisDateTime).isValid();
208 if (mDissociateOccurrences) {
209 mDissociateOccurrences->setEnabled(!(isFirstOccurrence && isLastOccurrence) && hasChangeRights);
210 }
211 }
212
213 // Enable/Disable menu items valid for sharing Incidences only
214 end = mShareOnlyItems.end();
215 for (it = mShareOnlyItems.begin(); it != end; ++it) {
216 (*it)->setVisible(true);
217 (*it)->setEnabled(true);
218 }
219
220 // Show the menu now
221 popup(QCursor::pos());
222 }
223
popupShow()224 void KOEventPopupMenu::popupShow()
225 {
226 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
227 Q_EMIT showIncidenceSignal(mCurrentIncidence);
228 }
229 }
230
popupEdit()231 void KOEventPopupMenu::popupEdit()
232 {
233 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
234 Q_EMIT editIncidenceSignal(mCurrentIncidence);
235 }
236 }
237
slotPrint()238 void KOEventPopupMenu::slotPrint()
239 {
240 print(false);
241 }
242
print(bool preview)243 void KOEventPopupMenu::print(bool preview)
244 {
245 CalendarSupport::CalPrinter printer(this, mCalendar, true);
246 connect(this, &KOEventPopupMenu::configChanged, &printer, &CalendarSupport::CalPrinter::updateConfig);
247
248 KCalendarCore::Incidence::List selectedIncidences;
249 Q_ASSERT(mCurrentIncidence.hasPayload<KCalendarCore::Incidence::Ptr>());
250 selectedIncidences.append(mCurrentIncidence.payload<KCalendarCore::Incidence::Ptr>());
251
252 printer.print(CalendarSupport::CalPrinterBase::Incidence, mCurrentDate, mCurrentDate, selectedIncidences, preview);
253 }
254
printPreview()255 void KOEventPopupMenu::printPreview()
256 {
257 print(true);
258 }
259
popupDelete()260 void KOEventPopupMenu::popupDelete()
261 {
262 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
263 Q_EMIT deleteIncidenceSignal(mCurrentIncidence);
264 }
265 }
266
popupCut()267 void KOEventPopupMenu::popupCut()
268 {
269 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
270 Q_EMIT cutIncidenceSignal(mCurrentIncidence);
271 }
272 }
273
popupCopy()274 void KOEventPopupMenu::popupCopy()
275 {
276 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
277 Q_EMIT copyIncidenceSignal(mCurrentIncidence);
278 }
279 }
280
popupPaste()281 void KOEventPopupMenu::popupPaste()
282 {
283 Q_EMIT pasteIncidenceSignal();
284 }
285
toggleAlarm()286 void KOEventPopupMenu::toggleAlarm()
287 {
288 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
289 Q_EMIT toggleAlarmSignal(mCurrentIncidence);
290 }
291 }
292
dissociateOccurrences()293 void KOEventPopupMenu::dissociateOccurrences()
294 {
295 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
296 Q_EMIT dissociateOccurrencesSignal(mCurrentIncidence, mCurrentDate);
297 }
298 }
299
forward()300 void KOEventPopupMenu::forward()
301 {
302 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
303 KCalendarCore::Incidence::Ptr incidence = CalendarSupport::incidence(mCurrentIncidence);
304 if (incidence) {
305 Akonadi::ITIPHandler handler(this);
306 handler.setCalendar(mCalendar);
307 handler.sendAsICalendar(incidence, this);
308 }
309 }
310 }
311
createEvent(const Akonadi::Item & item)312 void KOEventPopupMenu::createEvent(const Akonadi::Item &item)
313 {
314 mCurrentIncidence = item;
315 createEvent();
316 }
317
createEvent()318 void KOEventPopupMenu::createEvent()
319 {
320 // Must be a Incidence
321 if (!CalendarSupport::hasIncidence(mCurrentIncidence)) {
322 return;
323 }
324 // Event ->event doesn't make sense
325 if (CalendarSupport::hasEvent(mCurrentIncidence)) {
326 return;
327 }
328
329 if (CalendarSupport::hasTodo(mCurrentIncidence)) {
330 KCalendarCore::Todo::Ptr todo(CalendarSupport::todo(mCurrentIncidence));
331 KCalendarCore::Event::Ptr event(new KCalendarCore::Event(*todo));
332 event->setUid(KCalendarCore::CalFormat::createUniqueId());
333 event->setDtStart(todo->dtStart());
334 event->setAllDay(todo->allDay());
335 event->setDtEnd(todo->dtDue());
336 Akonadi::Item newEventItem;
337 newEventItem.setMimeType(KCalendarCore::Event::eventMimeType());
338 newEventItem.setPayload<KCalendarCore::Event::Ptr>(event);
339
340 IncidenceEditorNG::IncidenceDialog *dlg =
341 IncidenceEditorNG::IncidenceDialogFactory::create(true, KCalendarCore::IncidenceBase::TypeEvent, nullptr, this);
342 dlg->setObjectName(QStringLiteral("incidencedialog"));
343 dlg->load(newEventItem);
344 dlg->open();
345 }
346 }
347
createNote(const Akonadi::Item & item)348 void KOEventPopupMenu::createNote(const Akonadi::Item &item)
349 {
350 mCurrentIncidence = item;
351 createNote();
352 }
353
createNote()354 void KOEventPopupMenu::createNote()
355 {
356 // Must be a Incidence
357 if (CalendarSupport::hasIncidence(mCurrentIncidence)) {
358 KCalendarCore::Incidence::Ptr incidence(CalendarSupport::incidence(mCurrentIncidence));
359 Akonadi::NoteUtils::NoteMessageWrapper note;
360 note.setTitle(incidence->summary());
361 note.setText(incidence->description(), incidence->descriptionIsRich() ? Qt::RichText : Qt::PlainText);
362 note.setFrom(QCoreApplication::applicationName() + QCoreApplication::applicationVersion());
363 note.setLastModifiedDate(QDateTime::currentDateTimeUtc());
364 Akonadi::NoteUtils::Attachment attachment(mCurrentIncidence.url(), mCurrentIncidence.mimeType());
365 note.attachments().append(attachment);
366 Akonadi::Item newNoteItem;
367 newNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType());
368 newNoteItem.setPayload(note.message());
369
370 auto noteedit = new CalendarSupport::NoteEditDialog(this);
371 connect(noteedit, &CalendarSupport::NoteEditDialog::createNote, this, &KOEventPopupMenu::slotCreateNote);
372 noteedit->load(newNoteItem);
373 noteedit->show();
374 }
375 }
376
slotCreateNote(const Akonadi::Item & noteItem,const Akonadi::Collection & collection)377 void KOEventPopupMenu::slotCreateNote(const Akonadi::Item ¬eItem, const Akonadi::Collection &collection)
378 {
379 auto createJob = new Akonadi::ItemCreateJob(noteItem, collection, this);
380 connect(createJob, &Akonadi::ItemCreateJob::result, this, &KOEventPopupMenu::slotCreateNewNoteJobFinished);
381 createJob->start();
382 }
383
slotCreateNewNoteJobFinished(KJob * job)384 void KOEventPopupMenu::slotCreateNewNoteJobFinished(KJob *job)
385 {
386 if (job->error()) {
387 qCDebug(KORGANIZER_LOG) << "Error during create new Note " << job->errorString();
388 }
389 }
390
createTodo()391 void KOEventPopupMenu::createTodo()
392 {
393 // Must be a Incidence
394 if (!CalendarSupport::hasIncidence(mCurrentIncidence)) {
395 return;
396 }
397 // Todo->Todo doesn't make sense
398 if (CalendarSupport::hasTodo(mCurrentIncidence)) {
399 return;
400 }
401
402 if (CalendarSupport::hasEvent(mCurrentIncidence)) {
403 KCalendarCore::Event::Ptr event(CalendarSupport::event(mCurrentIncidence));
404 KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo(*event));
405 todo->setUid(KCalendarCore::CalFormat::createUniqueId());
406 todo->setDtStart(event->dtStart());
407 todo->setAllDay(event->allDay());
408 todo->setDtDue(event->dtEnd());
409 Akonadi::Item newTodoItem;
410 newTodoItem.setMimeType(KCalendarCore::Todo::todoMimeType());
411 newTodoItem.setPayload<KCalendarCore::Todo::Ptr>(todo);
412
413 IncidenceEditorNG::IncidenceDialog *dlg =
414 IncidenceEditorNG::IncidenceDialogFactory::create(true, KCalendarCore::IncidenceBase::TypeTodo, nullptr, this);
415 dlg->setObjectName(QStringLiteral("incidencedialog"));
416 dlg->load(newTodoItem);
417 dlg->open();
418 }
419 }
420
toggleTodoCompleted()421 void KOEventPopupMenu::toggleTodoCompleted()
422 {
423 if (CalendarSupport::hasTodo(mCurrentIncidence)) {
424 Q_EMIT toggleTodoCompletedSignal(mCurrentIncidence);
425 }
426 }
427
setCalendar(const Akonadi::ETMCalendar::Ptr & calendar)428 void KOEventPopupMenu::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar)
429 {
430 mCalendar = calendar;
431 }
432