1 /*
2     SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "messageactions.h"
8 
9 #include "kmcommands.h"
10 #include "kmkernel.h"
11 #include "kmmainwidget.h"
12 #include "kmreaderwin.h"
13 #include "settings/kmailsettings.h"
14 #include "util.h"
15 #include <MailCommon/MailKernel>
16 #include <TemplateParser/CustomTemplatesMenu>
17 
18 #include <MessageCore/MailingList>
19 #include <MessageCore/MessageCoreSettings>
20 #include <MessageCore/StringUtil>
21 #include <MessageViewer/HeaderStylePlugin>
22 #include <MessageViewer/MessageViewerSettings>
23 #include <PimCommonAkonadi/AnnotationDialog>
24 
25 #include <Akonadi/ChangeRecorder>
26 #include <Akonadi/ItemFetchJob>
27 #include <Akonadi/KMime/MessageParts>
28 #include <AkonadiSearch/Debug/akonadisearchdebugdialog.h>
29 #include <KIO/KUriFilterSearchProviderActions>
30 #include <QAction>
31 
32 #include "job/createfollowupreminderonexistingmessagejob.h"
33 #include <MessageComposer/FollowUpReminderSelectDateDialog>
34 
35 #include "kmail_debug.h"
36 #include <Akonadi/ItemFetchJob>
37 #include <KActionCollection>
38 #include <KActionMenu>
39 #include <KIO/JobUiDelegate>
40 #include <KIO/OpenUrlJob>
41 #include <KLocalizedString>
42 #include <KStringHandler>
43 #include <KUriFilter>
44 #include <KXMLGUIClient>
45 #include <QFileDialog>
46 #include <QIcon>
47 #include <QMenu>
48 
49 #include <Akonadi/Collection>
50 #include <Akonadi/EntityAnnotationsAttribute>
51 #include <MailCommon/MailUtil>
52 #include <MessageViewer/MessageViewerUtil>
53 #include <QVariant>
54 #include <QWidget>
55 
56 using namespace KMail;
57 
MessageActions(KActionCollection * ac,QWidget * parent)58 MessageActions::MessageActions(KActionCollection *ac, QWidget *parent)
59     : QObject(parent)
60     , mParent(parent)
61     , mReplyActionMenu(new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18nc("Message->", "&Reply"), this))
62     , mReplyAction(new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("&Reply..."), this))
63     , mReplyAllAction(new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n("Reply to &All..."), this))
64     , mReplyAuthorAction(new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("Reply to A&uthor..."), this))
65     , mReplyListAction(new QAction(QIcon::fromTheme(QStringLiteral("mail-reply-list")), i18n("Reply to Mailing-&List..."), this))
66     , mNoQuoteReplyAction(new QAction(i18n("Reply Without &Quote..."), this))
67     , mForwardInlineAction(new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu Message->Forward->", "&Inline..."), this))
68     , mForwardAttachedAction(
69           new QAction(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("@action:inmenu Message->Forward->", "As &Attachment..."), this))
70     , mRedirectAction(new QAction(i18nc("Message->Forward->", "&Redirect..."), this))
71     , mNewToRecipientsAction(new QAction(i18n("New Message to Recipients..."), this))
72     , mStatusMenu(new KActionMenu(i18n("Mar&k Message"), this))
73     , mForwardActionMenu(new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-forward")), i18nc("Message->", "&Forward"), this))
74     , mMailingListActionMenu(new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-message-new-list")), i18nc("Message->", "Mailing-&List"), this))
75     , mAnnotateAction(new QAction(QIcon::fromTheme(QStringLiteral("view-pim-notes")), i18n("Add Note..."), this))
76     , mEditAsNewAction(new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("&Edit As New"), this))
77     , mListFilterAction(new QAction(i18n("Filter on Mailing-&List..."), this))
78     , mAddFollowupReminderAction(new QAction(i18n("Add Followup Reminder..."), this))
79     , mDebugAkonadiSearchAction(new QAction(QStringLiteral("Debug Akonadi Search..."), this)) /* dont translate it*/
80     , mSendAgainAction(new QAction(i18n("Send A&gain..."), this))
81     , mNewMessageFromTemplateAction(new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New Message From &Template"), this))
82     , mWebShortcutMenuManager(new KIO::KUriFilterSearchProviderActions(this))
83     , mExportToPdfAction(new QAction(QIcon::fromTheme(QStringLiteral("application-pdf")), i18n("Export to PDF..."), this))
84 
85 {
86     ac->addAction(QStringLiteral("message_reply_menu"), mReplyActionMenu);
87     connect(mReplyActionMenu, &KActionMenu::triggered, this, &MessageActions::slotReplyToMsg);
88 
89     ac->addAction(QStringLiteral("reply"), mReplyAction);
90     ac->setDefaultShortcut(mReplyAction, Qt::Key_R);
91     connect(mReplyAction, &QAction::triggered, this, &MessageActions::slotReplyToMsg);
92     mReplyActionMenu->addAction(mReplyAction);
93 
94     ac->addAction(QStringLiteral("reply_author"), mReplyAuthorAction);
95     ac->setDefaultShortcut(mReplyAuthorAction, Qt::SHIFT | Qt::Key_A);
96     connect(mReplyAuthorAction, &QAction::triggered, this, &MessageActions::slotReplyAuthorToMsg);
97     mReplyActionMenu->addAction(mReplyAuthorAction);
98 
99     ac->addAction(QStringLiteral("reply_all"), mReplyAllAction);
100     ac->setDefaultShortcut(mReplyAllAction, Qt::Key_A);
101     connect(mReplyAllAction, &QAction::triggered, this, &MessageActions::slotReplyAllToMsg);
102     mReplyActionMenu->addAction(mReplyAllAction);
103 
104     ac->addAction(QStringLiteral("reply_list"), mReplyListAction);
105 
106     ac->setDefaultShortcut(mReplyListAction, Qt::Key_L);
107     connect(mReplyListAction, &QAction::triggered, this, &MessageActions::slotReplyListToMsg);
108     mReplyActionMenu->addAction(mReplyListAction);
109 
110     ac->addAction(QStringLiteral("noquotereply"), mNoQuoteReplyAction);
111     ac->setDefaultShortcut(mNoQuoteReplyAction, Qt::SHIFT | Qt::Key_R);
112     connect(mNoQuoteReplyAction, &QAction::triggered, this, &MessageActions::slotNoQuoteReplyToMsg);
113 
114     ac->addAction(QStringLiteral("mlist_filter"), mListFilterAction);
115     connect(mListFilterAction, &QAction::triggered, this, &MessageActions::slotMailingListFilter);
116 
117     ac->addAction(QStringLiteral("set_status"), mStatusMenu);
118 
119     ac->addAction(QStringLiteral("annotate"), mAnnotateAction);
120     connect(mAnnotateAction, &QAction::triggered, this, &MessageActions::annotateMessage);
121 
122     ac->addAction(QStringLiteral("editasnew"), mEditAsNewAction);
123     connect(mEditAsNewAction, &QAction::triggered, this, &MessageActions::editCurrentMessage);
124     ac->setDefaultShortcut(mEditAsNewAction, Qt::Key_T);
125 
126     mPrintAction = KStandardAction::print(this, &MessageActions::slotPrintMessage, ac);
127     mPrintPreviewAction = KStandardAction::printPreview(this, &MessageActions::slotPrintPreviewMsg, ac);
128 
129     ac->addAction(QStringLiteral("message_forward"), mForwardActionMenu);
130 
131     connect(mForwardAttachedAction, SIGNAL(triggered(bool)), parent, SLOT(slotForwardAttachedMessage()));
132 
133     ac->addAction(QStringLiteral("message_forward_as_attachment"), mForwardAttachedAction);
134 
135     connect(mForwardInlineAction, SIGNAL(triggered(bool)), parent, SLOT(slotForwardInlineMsg()));
136 
137     ac->addAction(QStringLiteral("message_forward_inline"), mForwardInlineAction);
138 
139     setupForwardActions(ac);
140 
141     ac->addAction(QStringLiteral("new_to_recipients"), mNewToRecipientsAction);
142     connect(mNewToRecipientsAction, SIGNAL(triggered(bool)), parent, SLOT(slotNewMessageToRecipients()));
143 
144     ac->addAction(QStringLiteral("message_forward_redirect"), mRedirectAction);
145     connect(mRedirectAction, SIGNAL(triggered(bool)), parent, SLOT(slotRedirectMessage()));
146 
147     ac->setDefaultShortcut(mRedirectAction, QKeySequence(Qt::Key_E));
148     mForwardActionMenu->addAction(mRedirectAction);
149 
150     connect(mMailingListActionMenu->menu(), &QMenu::triggered, this, &MessageActions::slotRunUrl);
151     ac->addAction(QStringLiteral("mailing_list"), mMailingListActionMenu);
152     mMailingListActionMenu->setEnabled(false);
153 
154     connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::itemChanged, this, &MessageActions::slotItemModified);
155     connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::itemRemoved, this, &MessageActions::slotItemRemoved);
156 
157     mCustomTemplatesMenu = new TemplateParser::CustomTemplatesMenu(parent, ac);
158 
159     connect(mCustomTemplatesMenu, SIGNAL(replyTemplateSelected(QString)), parent, SLOT(slotCustomReplyToMsg(QString)));
160     connect(mCustomTemplatesMenu, SIGNAL(replyAllTemplateSelected(QString)), parent, SLOT(slotCustomReplyAllToMsg(QString)));
161     connect(mCustomTemplatesMenu, SIGNAL(forwardTemplateSelected(QString)), parent, SLOT(slotCustomForwardMsg(QString)));
162     connect(KMKernel::self(), &KMKernel::customTemplatesChanged, mCustomTemplatesMenu, &TemplateParser::CustomTemplatesMenu::update);
163 
164     forwardMenu()->addSeparator();
165     forwardMenu()->addAction(mCustomTemplatesMenu->forwardActionMenu());
166     replyMenu()->addSeparator();
167     replyMenu()->addAction(mCustomTemplatesMenu->replyActionMenu());
168     replyMenu()->addAction(mCustomTemplatesMenu->replyAllActionMenu());
169 
170     // Don't translate it. Shown only when we set env variable AKONADI_SEARCH_DEBUG
171     connect(mDebugAkonadiSearchAction, &QAction::triggered, this, &MessageActions::slotDebugAkonadiSearch);
172 
173     ac->addAction(QStringLiteral("message_followup_reminder"), mAddFollowupReminderAction);
174     connect(mAddFollowupReminderAction, &QAction::triggered, this, &MessageActions::slotAddFollowupReminder);
175 
176     ac->addAction(QStringLiteral("send_again"), mSendAgainAction);
177     connect(mSendAgainAction, &QAction::triggered, this, &MessageActions::slotResendMessage);
178 
179     ac->addAction(QStringLiteral("use_template"), mNewMessageFromTemplateAction);
180     connect(mNewMessageFromTemplateAction, &QAction::triggered, this, &MessageActions::slotUseTemplate);
181     ac->setDefaultShortcut(mNewMessageFromTemplateAction, QKeySequence(Qt::SHIFT | Qt::Key_N));
182 
183     ac->addAction(QStringLiteral("file_export_pdf"), mExportToPdfAction);
184     connect(mExportToPdfAction, &QAction::triggered, this, &MessageActions::slotExportToPdf);
185 
186     updateActions();
187 }
188 
~MessageActions()189 MessageActions::~MessageActions()
190 {
191     delete mCustomTemplatesMenu;
192 }
193 
fillAkonadiStandardAction(Akonadi::StandardMailActionManager * akonadiStandardActionManager)194 void MessageActions::fillAkonadiStandardAction(Akonadi::StandardMailActionManager *akonadiStandardActionManager)
195 {
196     QAction *action = akonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkMailAsRead);
197     mStatusMenu->addAction(action);
198 
199     action = akonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkMailAsUnread);
200     mStatusMenu->addAction(action);
201 
202     mStatusMenu->addSeparator();
203     action = akonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkMailAsImportant);
204     mStatusMenu->addAction(action);
205 
206     action = akonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkMailAsActionItem);
207     mStatusMenu->addAction(action);
208 }
209 
customTemplatesMenu() const210 TemplateParser::CustomTemplatesMenu *MessageActions::customTemplatesMenu() const
211 {
212     return mCustomTemplatesMenu;
213 }
214 
slotUseTemplate()215 void MessageActions::slotUseTemplate()
216 {
217     if (!mCurrentItem.isValid()) {
218         return;
219     }
220     KMCommand *command = new KMUseTemplateCommand(mParent, mCurrentItem);
221     command->start();
222 }
223 
editAsNewAction() const224 QAction *MessageActions::editAsNewAction() const
225 {
226     return mEditAsNewAction;
227 }
228 
setCurrentMessage(const Akonadi::Item & msg,const Akonadi::Item::List & items)229 void MessageActions::setCurrentMessage(const Akonadi::Item &msg, const Akonadi::Item::List &items)
230 {
231     mCurrentItem = msg;
232 
233     if (!items.isEmpty()) {
234         if (msg.isValid()) {
235             mVisibleItems = items;
236         } else {
237             mVisibleItems.clear();
238         }
239     }
240 
241     if (!msg.isValid()) {
242         mVisibleItems.clear();
243         clearMailingListActions();
244     }
245 
246     updateActions();
247 }
248 
replyMenu() const249 KActionMenu *MessageActions::replyMenu() const
250 {
251     return mReplyActionMenu;
252 }
253 
replyListAction() const254 QAction *MessageActions::replyListAction() const
255 {
256     return mReplyListAction;
257 }
258 
forwardInlineAction() const259 QAction *MessageActions::forwardInlineAction() const
260 {
261     return mForwardInlineAction;
262 }
263 
forwardAttachedAction() const264 QAction *MessageActions::forwardAttachedAction() const
265 {
266     return mForwardAttachedAction;
267 }
268 
redirectAction() const269 QAction *MessageActions::redirectAction() const
270 {
271     return mRedirectAction;
272 }
273 
newToRecipientsAction() const274 QAction *MessageActions::newToRecipientsAction() const
275 {
276     return mNewToRecipientsAction;
277 }
278 
messageStatusMenu() const279 KActionMenu *MessageActions::messageStatusMenu() const
280 {
281     return mStatusMenu;
282 }
283 
forwardMenu() const284 KActionMenu *MessageActions::forwardMenu() const
285 {
286     return mForwardActionMenu;
287 }
288 
annotateAction() const289 QAction *MessageActions::annotateAction() const
290 {
291     return mAnnotateAction;
292 }
293 
printAction() const294 QAction *MessageActions::printAction() const
295 {
296     return mPrintAction;
297 }
298 
printPreviewAction() const299 QAction *MessageActions::printPreviewAction() const
300 {
301     return mPrintPreviewAction;
302 }
303 
listFilterAction() const304 QAction *MessageActions::listFilterAction() const
305 {
306     return mListFilterAction;
307 }
308 
mailingListActionMenu() const309 KActionMenu *MessageActions::mailingListActionMenu() const
310 {
311     return mMailingListActionMenu;
312 }
313 
slotItemRemoved(const Akonadi::Item & item)314 void MessageActions::slotItemRemoved(const Akonadi::Item &item)
315 {
316     if (item == mCurrentItem) {
317         mCurrentItem = Akonadi::Item();
318         updateActions();
319     }
320 }
321 
slotItemModified(const Akonadi::Item & item,const QSet<QByteArray> & partIdentifiers)322 void MessageActions::slotItemModified(const Akonadi::Item &item, const QSet<QByteArray> &partIdentifiers)
323 {
324     Q_UNUSED(partIdentifiers)
325     if (item == mCurrentItem) {
326         mCurrentItem = item;
327         const int numberOfVisibleItems = mVisibleItems.count();
328         for (int i = 0; i < numberOfVisibleItems; ++i) {
329             Akonadi::Item it = mVisibleItems.at(i);
330             if (item == it) {
331                 mVisibleItems[i] = item;
332             }
333         }
334         updateActions();
335     }
336 }
337 
updateActions()338 void MessageActions::updateActions()
339 {
340     const bool hasPayload = mCurrentItem.hasPayload<KMime::Message::Ptr>();
341     bool itemValid = mCurrentItem.isValid();
342     Akonadi::Collection parent;
343     if (itemValid) { //=> valid
344         parent = mCurrentItem.parentCollection();
345     }
346     if (parent.isValid()) {
347         if (CommonKernel->folderIsTemplates(parent)) {
348             itemValid = false;
349         }
350     }
351 
352     const bool multiVisible = !mVisibleItems.isEmpty() || mCurrentItem.isValid();
353     const bool uniqItem = (itemValid || hasPayload) && (mVisibleItems.count() <= 1);
354     mReplyActionMenu->setEnabled(hasPayload);
355     mReplyAction->setEnabled(hasPayload);
356     mNoQuoteReplyAction->setEnabled(hasPayload);
357     mReplyAuthorAction->setEnabled(hasPayload);
358     mReplyAllAction->setEnabled(hasPayload);
359     mReplyListAction->setEnabled(hasPayload);
360     mNoQuoteReplyAction->setEnabled(hasPayload);
361     mSendAgainAction->setEnabled(itemValid);
362 
363     mAnnotateAction->setEnabled(uniqItem);
364     mAddFollowupReminderAction->setEnabled(uniqItem);
365     if (!mCurrentItem.hasAttribute<Akonadi::EntityAnnotationsAttribute>()) {
366         mAnnotateAction->setText(i18n("Add Note..."));
367     } else {
368         mAnnotateAction->setText(i18n("Edit Note..."));
369     }
370 
371     mStatusMenu->setEnabled(multiVisible);
372 
373     mPrintAction->setEnabled(mMessageView != nullptr);
374     mPrintPreviewAction->setEnabled(mMessageView != nullptr);
375     mExportToPdfAction->setEnabled(uniqItem);
376     if (mCurrentItem.hasPayload<KMime::Message::Ptr>()) {
377         if (mCurrentItem.loadedPayloadParts().contains("RFC822")) {
378             updateMailingListActions(mCurrentItem);
379         } else {
380             auto job = new Akonadi::ItemFetchJob(mCurrentItem);
381             job->fetchScope().fetchAllAttributes();
382             job->fetchScope().fetchFullPayload(true);
383             job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Header);
384             job->fetchScope().fetchAttribute<Akonadi::EntityAnnotationsAttribute>();
385             connect(job, &Akonadi::ItemFetchJob::result, this, &MessageActions::slotUpdateActionsFetchDone);
386         }
387     }
388     mEditAsNewAction->setEnabled(uniqItem);
389 }
390 
slotUpdateActionsFetchDone(KJob * job)391 void MessageActions::slotUpdateActionsFetchDone(KJob *job)
392 {
393     if (job->error()) {
394         return;
395     }
396 
397     auto fetchJob = static_cast<Akonadi::ItemFetchJob *>(job);
398     if (fetchJob->items().isEmpty()) {
399         return;
400     }
401     const Akonadi::Item messageItem = fetchJob->items().constFirst();
402     if (messageItem == mCurrentItem) {
403         mCurrentItem = messageItem;
404         updateMailingListActions(messageItem);
405     }
406 }
407 
clearMailingListActions()408 void MessageActions::clearMailingListActions()
409 {
410     mMailingListActionMenu->setEnabled(false);
411     mListFilterAction->setEnabled(false);
412     mListFilterAction->setText(i18n("Filter on Mailing-List..."));
413 }
414 
updateMailingListActions(const Akonadi::Item & messageItem)415 void MessageActions::updateMailingListActions(const Akonadi::Item &messageItem)
416 {
417     if (!messageItem.hasPayload<KMime::Message::Ptr>()) {
418         return;
419     }
420     auto message = messageItem.payload<KMime::Message::Ptr>();
421     const MessageCore::MailingList mailList = MessageCore::MailingList::detect(message);
422 
423     if (mailList.features() == MessageCore::MailingList::None) {
424         clearMailingListActions();
425     } else {
426         // A mailing list menu with only a title is pretty boring
427         // so make sure theres at least some content
428         QString listId;
429         if (mailList.features() & MessageCore::MailingList::Id) {
430             // From a list-id in the form, "Birds of France <bof.yahoo.com>",
431             // take "Birds of France" if it exists otherwise "bof.yahoo.com".
432             listId = mailList.id();
433             const int start = listId.indexOf(QLatin1Char('<'));
434             if (start > 0) {
435                 listId.truncate(start - 1);
436             } else if (start == 0) {
437                 const int end = listId.lastIndexOf(QLatin1Char('>'));
438                 if (end < 1) { // shouldn't happen but account for it anyway
439                     listId.remove(0, 1);
440                 } else {
441                     listId = listId.mid(1, end - 1);
442                 }
443             }
444         }
445         mMailingListActionMenu->menu()->clear();
446         qDeleteAll(mMailListActionList);
447         mMailListActionList.clear();
448         mMailingListActionMenu->menu()->setTitle(KStringHandler::rsqueeze(i18n("Mailing List Name: %1", (listId.isEmpty() ? i18n("<unknown>") : listId)), 40));
449         if (mailList.features() & MessageCore::MailingList::ArchivedAt) {
450             // IDEA: this may be something you want to copy - "Copy in submenu"?
451             addMailingListActions(i18n("Open Message in List Archive"), mailList.archivedAtUrls());
452         }
453         if (mailList.features() & MessageCore::MailingList::Post) {
454             addMailingListActions(i18n("Post New Message"), mailList.postUrls());
455         }
456         if (mailList.features() & MessageCore::MailingList::Archive) {
457             addMailingListActions(i18n("Go to Archive"), mailList.archiveUrls());
458         }
459         if (mailList.features() & MessageCore::MailingList::Help) {
460             addMailingListActions(i18n("Request Help"), mailList.helpUrls());
461         }
462         if (mailList.features() & MessageCore::MailingList::Owner) {
463             addMailingListActions(i18nc("Contact the owner of the mailing list", "Contact Owner"), mailList.ownerUrls());
464         }
465         if (mailList.features() & MessageCore::MailingList::Subscribe) {
466             addMailingListActions(i18n("Subscribe to List"), mailList.subscribeUrls());
467         }
468         if (mailList.features() & MessageCore::MailingList::Unsubscribe) {
469             addMailingListActions(i18n("Unsubscribe from List"), mailList.unsubscribeUrls());
470         }
471         mMailingListActionMenu->setEnabled(true);
472 
473         QByteArray name;
474         QString value;
475         const QString lname = MailingList::name(message, name, value);
476         if (!lname.isEmpty()) {
477             mListFilterAction->setEnabled(true);
478             mListFilterAction->setText(i18n("Filter on Mailing-List %1...", lname));
479         }
480     }
481 }
482 
replyCommand(MessageComposer::ReplyStrategy strategy)483 void MessageActions::replyCommand(MessageComposer::ReplyStrategy strategy)
484 {
485     if (!mCurrentItem.hasPayload<KMime::Message::Ptr>()) {
486         return;
487     }
488 
489     const QString text = mMessageView ? mMessageView->copyText() : QString();
490     auto command = new KMReplyCommand(mParent, mCurrentItem, strategy, text);
491     command->setReplyAsHtml(mMessageView ? mMessageView->viewer()->htmlMail() : false);
492     connect(command, &KMCommand::completed, this, &MessageActions::replyActionFinished);
493     command->start();
494 }
495 
setMessageView(KMReaderWin * msgView)496 void MessageActions::setMessageView(KMReaderWin *msgView)
497 {
498     mMessageView = msgView;
499 }
500 
setupForwardActions(KActionCollection * ac)501 void MessageActions::setupForwardActions(KActionCollection *ac)
502 {
503     disconnect(mForwardActionMenu, SIGNAL(triggered(bool)), nullptr, nullptr);
504     mForwardActionMenu->removeAction(mForwardInlineAction);
505     mForwardActionMenu->removeAction(mForwardAttachedAction);
506 
507     if (KMailSettings::self()->forwardingInlineByDefault()) {
508         mForwardActionMenu->insertAction(mRedirectAction, mForwardInlineAction);
509         mForwardActionMenu->insertAction(mRedirectAction, mForwardAttachedAction);
510         ac->setDefaultShortcut(mForwardInlineAction, QKeySequence(Qt::Key_F));
511         ac->setDefaultShortcut(mForwardAttachedAction, QKeySequence(Qt::SHIFT | Qt::Key_F));
512         QObject::connect(mForwardActionMenu, SIGNAL(triggered(bool)), mParent, SLOT(slotForwardInlineMsg()));
513     } else {
514         mForwardActionMenu->insertAction(mRedirectAction, mForwardAttachedAction);
515         mForwardActionMenu->insertAction(mRedirectAction, mForwardInlineAction);
516         ac->setDefaultShortcut(mForwardInlineAction, QKeySequence(Qt::Key_F));
517         ac->setDefaultShortcut(mForwardAttachedAction, QKeySequence(Qt::SHIFT | Qt::Key_F));
518         QObject::connect(mForwardActionMenu, SIGNAL(triggered(bool)), mParent, SLOT(slotForwardAttachedMessage()));
519     }
520 }
521 
setupForwardingActionsList(KXMLGUIClient * guiClient)522 void MessageActions::setupForwardingActionsList(KXMLGUIClient *guiClient)
523 {
524     QList<QAction *> forwardActionList;
525     guiClient->unplugActionList(QStringLiteral("forward_action_list"));
526     if (KMailSettings::self()->forwardingInlineByDefault()) {
527         forwardActionList.append(mForwardInlineAction);
528         forwardActionList.append(mForwardAttachedAction);
529     } else {
530         forwardActionList.append(mForwardAttachedAction);
531         forwardActionList.append(mForwardInlineAction);
532     }
533     forwardActionList.append(mRedirectAction);
534     guiClient->plugActionList(QStringLiteral("forward_action_list"), forwardActionList);
535 }
536 
slotReplyToMsg()537 void MessageActions::slotReplyToMsg()
538 {
539     replyCommand(MessageComposer::ReplySmart);
540 }
541 
slotReplyAuthorToMsg()542 void MessageActions::slotReplyAuthorToMsg()
543 {
544     replyCommand(MessageComposer::ReplyAuthor);
545 }
546 
slotReplyListToMsg()547 void MessageActions::slotReplyListToMsg()
548 {
549     replyCommand(MessageComposer::ReplyList);
550 }
551 
slotReplyAllToMsg()552 void MessageActions::slotReplyAllToMsg()
553 {
554     replyCommand(MessageComposer::ReplyAll);
555 }
556 
slotNoQuoteReplyToMsg()557 void MessageActions::slotNoQuoteReplyToMsg()
558 {
559     if (!mCurrentItem.hasPayload<KMime::Message::Ptr>()) {
560         return;
561     }
562     auto command = new KMReplyCommand(mParent, mCurrentItem, MessageComposer::ReplySmart, QString(), true);
563     command->setReplyAsHtml(mMessageView ? mMessageView->viewer()->htmlMail() : false);
564 
565     command->start();
566 }
567 
slotRunUrl(QAction * urlAction)568 void MessageActions::slotRunUrl(QAction *urlAction)
569 {
570     const QVariant q = urlAction->data();
571     if (q.type() == QVariant::Url) {
572         auto job = new KIO::OpenUrlJob(q.toUrl());
573         job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mParent));
574         job->start();
575     }
576 }
577 
slotMailingListFilter()578 void MessageActions::slotMailingListFilter()
579 {
580     if (!mCurrentItem.hasPayload<KMime::Message::Ptr>()) {
581         return;
582     }
583 
584     KMCommand *command = new KMMailingListFilterCommand(mParent, mCurrentItem);
585     command->start();
586 }
587 
printMessage(bool preview)588 void MessageActions::printMessage(bool preview)
589 {
590     if (mMessageView) {
591         bool result = false;
592         if (MessageViewer::MessageViewerSettings::self()->printSelectedText()) {
593             result = mMessageView->printSelectedText(preview);
594         }
595         if (!result) {
596             const bool useFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont();
597             const QString overrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding();
598 
599             const Akonadi::Item message = mCurrentItem;
600             KMPrintCommandInfo commandInfo;
601             commandInfo.mMsg = message;
602             commandInfo.mHeaderStylePlugin = mMessageView->viewer()->headerStylePlugin();
603             commandInfo.mFormat = mMessageView->viewer()->displayFormatMessageOverwrite();
604             commandInfo.mHtmlLoadExtOverride = mMessageView->viewer()->htmlLoadExternal();
605             commandInfo.mPrintPreview = preview;
606             commandInfo.mUseFixedFont = useFixedFont;
607             commandInfo.mOverrideFont = overrideEncoding;
608             commandInfo.mShowSignatureDetails =
609                 mMessageView->viewer()->showSignatureDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails();
610             commandInfo.mShowEncryptionDetails =
611                 mMessageView->viewer()->showEncryptionDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails();
612 
613             auto command = new KMPrintCommand(mParent, commandInfo);
614             command->start();
615         }
616     } else {
617         qCWarning(KMAIL_LOG) << "MessageActions::printMessage impossible to do it if we don't have a viewer";
618     }
619 }
620 
slotPrintPreviewMsg()621 void MessageActions::slotPrintPreviewMsg()
622 {
623     printMessage(true);
624 }
625 
slotPrintMessage()626 void MessageActions::slotPrintMessage()
627 {
628     printMessage(false);
629 }
630 
631 /**
632  * This adds a list of actions to mMailingListActionMenu mapping the identifier item to
633  * the url.
634  *
635  * e.g.: item = "Contact Owner"
636  * "Contact Owner (email)" -> KRun( "mailto:bob@arthouseflowers.example.com" )
637  * "Contact Owner (web)" -> KRun( "http://arthouseflowers.example.com/contact-owner.php" )
638  */
addMailingListActions(const QString & item,const QList<QUrl> & list)639 void MessageActions::addMailingListActions(const QString &item, const QList<QUrl> &list)
640 {
641     for (const QUrl &url : list) {
642         addMailingListAction(item, url);
643     }
644 }
645 
646 /**
647  * This adds a action to mMailingListActionMenu mapping the identifier item to
648  * the url. See addMailingListActions above.
649  */
addMailingListAction(const QString & item,const QUrl & url)650 void MessageActions::addMailingListAction(const QString &item, const QUrl &url)
651 {
652     QString protocol = url.scheme().toLower();
653     QString prettyUrl = url.toDisplayString();
654     if (protocol == QLatin1String("mailto")) {
655         protocol = i18n("email");
656         prettyUrl.remove(0, 7); // length( "mailto:" )
657     } else if (protocol.startsWith(QLatin1String("http"))) {
658         protocol = i18n("web");
659     }
660     // item is a mailing list url description passed from the updateActions method above.
661     auto *act =
662         new QAction(i18nc("%1 is a 'Contact Owner' or similar action. %2 is a protocol normally web or email though could be irc/ftp or other url variant",
663                           "%1 (%2)",
664                           item,
665                           protocol),
666                     this);
667     mMailListActionList.append(act);
668     const QVariant v(url);
669     act->setData(v);
670     KMail::Util::addQActionHelpText(act, prettyUrl);
671     mMailingListActionMenu->addAction(act);
672 }
673 
editCurrentMessage()674 void MessageActions::editCurrentMessage()
675 {
676     KMCommand *command = nullptr;
677     if (mCurrentItem.isValid()) {
678         Akonadi::Collection col = mCurrentItem.parentCollection();
679         qCDebug(KMAIL_LOG) << " mCurrentItem.parentCollection()" << mCurrentItem.parentCollection();
680         // edit, unlike send again, removes the message from the folder
681         // we only want that for templates and drafts folders
682         if (col.isValid() && (CommonKernel->folderIsDraftOrOutbox(col) || CommonKernel->folderIsTemplates(col))) {
683             command = new KMEditItemCommand(mParent, mCurrentItem, true);
684         } else {
685             command = new KMEditItemCommand(mParent, mCurrentItem, false);
686         }
687         command->start();
688     } else if (mCurrentItem.hasPayload<KMime::Message::Ptr>()) {
689         command = new KMEditMessageCommand(mParent, mCurrentItem.payload<KMime::Message::Ptr>());
690         command->start();
691     }
692 }
693 
annotateMessage()694 void MessageActions::annotateMessage()
695 {
696     if (!mCurrentItem.isValid()) {
697         return;
698     }
699 
700     PimCommon::AnnotationEditDialog dialog(mCurrentItem, mParent);
701     dialog.setAttribute(Qt::WA_DeleteOnClose);
702     dialog.exec();
703 }
704 
addWebShortcutsMenu(QMenu * menu,const QString & text)705 void MessageActions::addWebShortcutsMenu(QMenu *menu, const QString &text)
706 {
707     mWebShortcutMenuManager->setSelectedText(text);
708     mWebShortcutMenuManager->addWebShortcutsToMenu(menu);
709 }
710 
debugAkonadiSearchAction() const711 QAction *MessageActions::debugAkonadiSearchAction() const
712 {
713     return mDebugAkonadiSearchAction;
714 }
715 
addFollowupReminderAction() const716 QAction *MessageActions::addFollowupReminderAction() const
717 {
718     return mAddFollowupReminderAction;
719 }
720 
slotDebugAkonadiSearch()721 void MessageActions::slotDebugAkonadiSearch()
722 {
723     if (!mCurrentItem.isValid()) {
724         return;
725     }
726     QPointer<Akonadi::Search::AkonadiSearchDebugDialog> dlg = new Akonadi::Search::AkonadiSearchDebugDialog;
727     dlg->setAkonadiId(mCurrentItem.id());
728     dlg->setAttribute(Qt::WA_DeleteOnClose);
729     dlg->setSearchType(Akonadi::Search::AkonadiSearchDebugSearchPathComboBox::Emails);
730     dlg->doSearch();
731     dlg->show();
732 }
733 
slotResendMessage()734 void MessageActions::slotResendMessage()
735 {
736     if (!mCurrentItem.isValid()) {
737         return;
738     }
739     KMCommand *command = new KMResendMessageCommand(mParent, mCurrentItem);
740     command->start();
741 }
742 
newMessageFromTemplateAction() const743 QAction *MessageActions::newMessageFromTemplateAction() const
744 {
745     return mNewMessageFromTemplateAction;
746 }
747 
sendAgainAction() const748 QAction *MessageActions::sendAgainAction() const
749 {
750     return mSendAgainAction;
751 }
752 
slotAddFollowupReminder()753 void MessageActions::slotAddFollowupReminder()
754 {
755     if (!mCurrentItem.isValid()) {
756         return;
757     }
758 
759     QPointer<MessageComposer::FollowUpReminderSelectDateDialog> dlg = new MessageComposer::FollowUpReminderSelectDateDialog(mParent);
760     if (dlg->exec()) {
761         const QDate date = dlg->selectedDate();
762         auto job = new CreateFollowupReminderOnExistingMessageJob(this);
763         job->setDate(date);
764         job->setCollection(dlg->collection());
765         job->setMessageItem(mCurrentItem);
766         job->start();
767     }
768     delete dlg;
769 }
770 
slotExportToPdf()771 void MessageActions::slotExportToPdf()
772 {
773     QString fileName = MessageViewer::Util::generateFileNameForExtension(mCurrentItem, QStringLiteral(".pdf"));
774     fileName = QFileDialog::getSaveFileName(mParent, i18n("Export to PDF"), QDir::homePath() + QLatin1Char('/') + fileName, i18n("PDF document (*.pdf)"));
775     if (!fileName.isEmpty()) {
776         mMessageView->viewer()->exportToPdf(fileName);
777     }
778 }
779 
currentItem() const780 Akonadi::Item MessageActions::currentItem() const
781 {
782     return mCurrentItem;
783 }
784 
exportToPdfAction() const785 QAction *MessageActions::exportToPdfAction() const
786 {
787     return mExportToPdfAction;
788 }
789