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 &noteItem, 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