Fix Bug 361605 - kmail crash on double click email
[kdepim.git] / kmail / kmcommands.cpp
blobce9bca89f8f5e2bf8acac7d4a8f39ad066303a2e
1 /*
2 This file is part of KMail, the KDE mail client.
3 Copyright (c) 2002 Don Sanders <sanders@kde.org>
4 Copyright (C) 2013-2016 Laurent Montel <montel@kde.org>
6 KMail is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License, version 2, as
8 published by the Free Software Foundation.
10 KMail is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 // This file implements various "command" classes. These command classes
22 // are based on the command design pattern.
24 // Historically various operations were implemented as slots of KMMainWin.
25 // This proved inadequate as KMail has multiple top level windows
26 // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
27 // benefit from using these operations. It is desirable that these
28 // classes can operate without depending on or altering the state of
29 // a KMMainWin, in fact it is possible no KMMainWin object even exists.
31 // Now these operations have been rewritten as KMCommand based classes,
32 // making them independent of KMMainWin.
34 // The base command class KMCommand is async, which is a difference
35 // from the conventional command pattern. As normal derived classes implement
36 // the execute method, but client classes call start() instead of
37 // calling execute() directly. start() initiates async operations,
38 // and on completion of these operations calls execute() and then deletes
39 // the command. (So the client must not construct commands on the stack).
41 // The type of async operation supported by KMCommand is retrieval
42 // of messages from an IMAP server.
44 #include "kmcommands.h"
46 #include "widgets/collectionpane.h"
47 #include "kmreadermainwin.h"
48 #include "secondarywindow.h"
49 #include "util.h"
50 #include "settings/kmailsettings.h"
51 #include "kmail_debug.h"
53 #include "editor/composer.h"
54 #include "kmmainwidget.h"
55 #include "undostack.h"
57 #include <KIdentityManagement/IdentityManager>
59 #include <KMime/Message>
60 #include <KMime/MDN>
62 #include <AkonadiCore/ItemModifyJob>
63 #include <AkonadiCore/ItemFetchJob>
64 #include <AkonadiCore/ItemMoveJob>
65 #include <AkonadiCore/ItemCopyJob>
66 #include <AkonadiCore/ItemDeleteJob>
67 #include <AkonadiCore/Tag>
68 #include <AkonadiCore/TagCreateJob>
70 #include <MailCommon/FolderCollection>
71 #include <MailCommon/FilterAction>
72 #include <MailCommon/FilterManager>
73 #include <MailCommon/MailFilter>
74 #include <MailCommon/RedirectDialog>
75 #include <MailCommon/MDNStateAttribute>
76 #include <MailCommon/MailKernel>
77 #include <MailCommon/MailUtil>
79 #include <MessageCore/StringUtil>
80 #include <MessageCore/MessageCoreSettings>
81 #include <MessageCore/StringUtil>
82 #include <MessageCore/MessageHelpers>
83 #include <MessageCore/MailingList>
85 #include <MessageComposer/MessageSender>
86 #include <MessageComposer/MessageHelper>
87 #include <MessageComposer/MessageComposerSettings>
88 #include <MessageComposer/MessageFactory>
89 #include <MessageComposer/Util>
91 #include <MessageList/Pane>
93 #include <MessageViewer/CSSHelper>
94 #include <MessageViewer/HeaderStylePlugin>
95 #include <MessageViewer/MessageViewerSettings>
96 #include <MessageViewer/MessageViewerUtil>
97 #include <MessageViewer/NodeHelper>
98 #include <MessageViewer/ObjectTreeEmptySource>
99 #include <MessageViewer/ObjectTreeParser>
101 #include <MailTransport/TransportAttribute>
102 #include <MailTransport/SentBehaviourAttribute>
103 #include <MailTransport/TransportManager>
105 #include <Libkdepim/ProgressManager>
106 #include <Libkdepim/BroadcastStatus>
107 #ifndef QT_NO_CURSOR
108 #include <Libkdepim/KCursorSaver>
109 #endif
111 #include <Libkleo/CryptoBackend>
112 #include <Libkleo/CryptoBackendFactory>
114 #include <gpgme++/error.h>
116 #include <boost/bind.hpp>
117 #include <algorithm>
118 #include <memory>
119 #include <unistd.h> // link()
120 #include <KBookmarkManager>
122 #include <KDBusServiceStarter>
123 #include <KEmailAddress>
124 #include <KFileWidget>
125 #include <KJobWidgets>
126 #include <KLocalizedString>
127 #include <KMessageBox>
128 #include <KRecentDirs>
130 // KIO headers
131 #include <KIO/Job>
132 #include <KIO/JobUiDelegate>
133 #include <KIO/StatJob>
135 #include <QApplication>
136 #include <QByteArray>
137 #include <QFileDialog>
138 #include <QFontDatabase>
139 #include <QList>
140 #include <QProgressDialog>
141 #include <QStandardPaths>
143 using KMail::SecondaryWindow;
144 using MailTransport::TransportManager;
145 using MessageComposer::MessageFactory;
147 using KPIM::ProgressManager;
148 using KPIM::ProgressItem;
149 using namespace KMime;
151 using namespace MailCommon;
153 /// Small helper function to get the composer context from a reply
154 static KMail::Composer::TemplateContext replyContext(MessageFactory::MessageReply reply)
156 if (reply.replyAll) {
157 return KMail::Composer::ReplyToAll;
158 } else {
159 return KMail::Composer::Reply;
163 /// Helper to sanely show an error message for a job
164 static void showJobError(KJob *job)
166 assert(job);
167 // we can be called from the KJob::kill, where we are no longer a KIO::Job
168 // so better safe than sorry
169 KIO::Job *kiojob = dynamic_cast<KIO::Job *>(job);
170 if (kiojob && kiojob->ui()) {
171 kiojob->ui()->showErrorMessage();
172 } else {
173 qCWarning(KMAIL_LOG) << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString();
177 KMCommand::KMCommand(QWidget *parent)
178 : mCountMsgs(0), mResult(Undefined), mDeletesItself(false),
179 mEmitsCompletedItself(false), mParent(parent)
183 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item &msg)
184 : mCountMsgs(0), mResult(Undefined), mDeletesItself(false),
185 mEmitsCompletedItself(false), mParent(parent)
187 if (msg.isValid() || msg.hasPayload<KMime::Message::Ptr>()) {
188 mMsgList.append(msg);
192 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item::List &msgList)
193 : mCountMsgs(0), mResult(Undefined), mDeletesItself(false),
194 mEmitsCompletedItself(false), mParent(parent)
196 mMsgList = msgList;
199 KMCommand::~KMCommand()
203 KMCommand::Result KMCommand::result() const
205 if (mResult == Undefined) {
206 qCDebug(KMAIL_LOG) << "mResult is Undefined";
208 return mResult;
211 const Akonadi::Item::List KMCommand::retrievedMsgs() const
213 return mRetrievedMsgs;
216 Akonadi::Item KMCommand::retrievedMessage() const
218 if (mRetrievedMsgs.isEmpty()) {
219 return Akonadi::Item();
221 return *(mRetrievedMsgs.begin());
224 QWidget *KMCommand::parentWidget() const
226 return mParent;
229 bool KMCommand::deletesItself() const
231 return mDeletesItself;
234 void KMCommand::setDeletesItself(bool deletesItself)
236 mDeletesItself = deletesItself;
239 bool KMCommand::emitsCompletedItself() const
241 return mEmitsCompletedItself;
244 void KMCommand::setEmitsCompletedItself(bool emitsCompletedItself)
246 mEmitsCompletedItself = emitsCompletedItself;
249 void KMCommand::setResult(KMCommand::Result result)
251 mResult = result;
254 int KMCommand::mCountJobs = 0;
256 void KMCommand::start()
258 connect(this, &KMCommand::messagesTransfered,
259 this, &KMCommand::slotPostTransfer);
261 if (mMsgList.isEmpty()) {
262 Q_EMIT messagesTransfered(OK);
263 return;
266 // Special case of operating on message that isn't in a folder
267 const Akonadi::Item mb = mMsgList.first();
268 if ((mMsgList.count() == 1) && MessageCore::Util::isStandaloneMessage(mb)) {
269 mRetrievedMsgs.append(mMsgList.takeFirst());
270 Q_EMIT messagesTransfered(OK);
271 return;
274 // we can only retrieve items with a valid id
275 foreach (const Akonadi::Item &item, mMsgList) {
276 if (!item.isValid()) {
277 Q_EMIT messagesTransfered(Failed);
278 return;
282 // transfer the selected messages first
283 transferSelectedMsgs();
286 void KMCommand::slotPostTransfer(KMCommand::Result result)
288 disconnect(this, &KMCommand::messagesTransfered,
289 this, &KMCommand::slotPostTransfer);
290 if (result == OK) {
291 result = execute();
293 mResult = result;
294 if (!emitsCompletedItself()) {
295 Q_EMIT completed(this);
297 if (!deletesItself()) {
298 deleteLater();
302 Akonadi::ItemFetchJob *KMCommand::createFetchJob(const Akonadi::Item::List &items)
304 return new Akonadi::ItemFetchJob(items, this);
307 void KMCommand::transferSelectedMsgs()
309 // make sure no other transfer is active
310 if (KMCommand::mCountJobs > 0) {
311 Q_EMIT messagesTransfered(Failed);
312 return;
315 bool complete = true;
316 KMCommand::mCountJobs = 0;
317 mCountMsgs = 0;
318 mRetrievedMsgs.clear();
319 mCountMsgs = mMsgList.count();
320 uint totalSize = 0;
321 // the QProgressDialog for the user-feedback. Only enable it if it's needed.
322 // For some commands like KMSetStatusCommand it's not needed. Note, that
323 // for some reason the QProgressDialog eats the MouseReleaseEvent (if a
324 // command is executed after the MousePressEvent), cf. bug #71761.
325 if (mCountMsgs > 0) {
326 mProgressDialog = new QProgressDialog(mParent);
327 mProgressDialog.data()->setWindowTitle(i18n("Please wait"));
329 mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count()));
330 mProgressDialog.data()->setModal(true);
331 mProgressDialog.data()->setMinimumDuration(1000);
334 // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever
335 if (!mFetchScope.isEmpty()) {
336 complete = false;
337 ++KMCommand::mCountJobs;
338 Akonadi::ItemFetchJob *fetch = createFetchJob(mMsgList);
339 mFetchScope.fetchAttribute< MailCommon::MDNStateAttribute >();
340 fetch->setFetchScope(mFetchScope);
341 connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, &KMCommand::slotMsgTransfered);
342 connect(fetch, &Akonadi::ItemFetchJob::result, this, &KMCommand::slotJobFinished);
343 } else {
344 // no need to fetch anything
345 if (!mMsgList.isEmpty()) {
346 mRetrievedMsgs = mMsgList;
350 if (complete) {
351 delete mProgressDialog.data();
352 mProgressDialog.clear();
353 Q_EMIT messagesTransfered(OK);
354 } else {
355 // wait for the transfer and tell the progressBar the necessary steps
356 if (mProgressDialog.data()) {
357 connect(mProgressDialog.data(), &QProgressDialog::canceled,
358 this, &KMCommand::slotTransferCancelled);
359 mProgressDialog.data()->setMaximum(totalSize);
364 void KMCommand::slotMsgTransfered(const Akonadi::Item::List &msgs)
366 if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
367 Q_EMIT messagesTransfered(Canceled);
368 return;
370 // save the complete messages
371 mRetrievedMsgs.append(msgs);
374 void KMCommand::slotJobFinished()
376 // the job is finished (with / without error)
377 KMCommand::mCountJobs--;
379 if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
380 return;
383 if (mCountMsgs > mRetrievedMsgs.count()) {
384 // the message wasn't retrieved before => error
385 if (mProgressDialog.data()) {
386 mProgressDialog.data()->hide();
388 slotTransferCancelled();
389 return;
391 // update the progressbar
392 if (mProgressDialog.data()) {
393 mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred",
394 "Please wait while the %1 messages are transferred", KMCommand::mCountJobs));
396 if (KMCommand::mCountJobs == 0) {
397 // all done
398 delete mProgressDialog.data();
399 mProgressDialog.clear();
400 Q_EMIT messagesTransfered(OK);
404 void KMCommand::slotTransferCancelled()
406 KMCommand::mCountJobs = 0;
407 mCountMsgs = 0;
408 mRetrievedMsgs.clear();
409 Q_EMIT messagesTransfered(Canceled);
412 KMMailtoComposeCommand::KMMailtoComposeCommand(const QUrl &url,
413 const Akonadi::Item &msg)
414 : mUrl(url), mMessage(msg)
418 KMCommand::Result KMMailtoComposeCommand::execute()
420 KMime::Message::Ptr msg(new KMime::Message);
421 uint id = 0;
423 if (mMessage.isValid() && mMessage.parentCollection().isValid()) {
424 QSharedPointer<FolderCollection> fd = FolderCollection::forCollection(mMessage.parentCollection(), false);
425 id = fd->identity();
428 MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
429 msg->contentType()->setCharset("utf-8");
430 msg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8");
432 KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
433 win->setFocusToSubject();
434 win->show();
435 return OK;
438 KMMailtoReplyCommand::KMMailtoReplyCommand(QWidget *parent,
439 const QUrl &url, const Akonadi::Item &msg, const QString &selection)
440 : KMCommand(parent, msg), mUrl(url), mSelection(selection)
442 fetchScope().fetchFullPayload(true);
443 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
446 KMCommand::Result KMMailtoReplyCommand::execute()
448 Akonadi::Item item = retrievedMessage();
449 KMime::Message::Ptr msg = MessageCore::Util::message(item);
450 if (!msg) {
451 return Failed;
453 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
454 factory.setIdentityManager(KMKernel::self()->identityManager());
455 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
456 factory.setMailingListAddresses(KMail::Util::mailingListsFromMessage(item));
457 factory.putRepliesInSameFolder(KMail::Util::putRepliesInSameFolder(item));
458 factory.setReplyStrategy(MessageComposer::ReplyNone);
459 factory.setSelection(mSelection);
460 KMime::Message::Ptr rmsg = factory.createReply().msg;
461 rmsg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8"); //TODO Check the UTF-8
462 bool lastEncrypt = false;
463 bool lastSign = false;
464 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
466 KMail::Composer *win = KMail::makeComposer(rmsg, lastSign, lastEncrypt, KMail::Composer::Reply, 0, mSelection);
467 win->setFocusToEditor();
468 win->show();
470 return OK;
473 KMMailtoForwardCommand::KMMailtoForwardCommand(QWidget *parent,
474 const QUrl &url, const Akonadi::Item &msg)
475 : KMCommand(parent, msg), mUrl(url)
477 fetchScope().fetchFullPayload(true);
478 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
481 KMCommand::Result KMMailtoForwardCommand::execute()
483 //TODO : consider factoring createForward into this method.
484 Akonadi::Item item = retrievedMessage();
485 KMime::Message::Ptr msg = MessageCore::Util::message(item);
486 if (!msg) {
487 return Failed;
489 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
490 factory.setIdentityManager(KMKernel::self()->identityManager());
491 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
492 KMime::Message::Ptr fmsg = factory.createForward();
493 fmsg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl).toLower(), "utf-8"); //TODO check the utf-8
494 bool lastEncrypt = false;
495 bool lastSign = false;
496 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
498 KMail::Composer *win = KMail::makeComposer(fmsg, lastSign, lastEncrypt, KMail::Composer::Forward);
499 win->show();
501 return OK;
504 KMAddBookmarksCommand::KMAddBookmarksCommand(const QUrl &url, QWidget *parent)
505 : KMCommand(parent), mUrl(url)
509 KMCommand::Result KMAddBookmarksCommand::execute()
511 const QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + QLatin1String("konqueror/bookmarks.xml");
512 QFileInfo fileInfo(filename);
513 QDir().mkpath(fileInfo.absolutePath());
514 KBookmarkManager *bookManager = KBookmarkManager::managerForFile(filename, QStringLiteral("konqueror"));
515 KBookmarkGroup group = bookManager->root();
516 group.addBookmark(mUrl.path(), QUrl(mUrl), QString());
517 if (bookManager->save()) {
518 bookManager->emitChanged(group);
521 return OK;
524 KMUrlSaveCommand::KMUrlSaveCommand(const QUrl &url, QWidget *parent)
525 : KMCommand(parent), mUrl(url)
529 KMCommand::Result KMUrlSaveCommand::execute()
531 if (mUrl.isEmpty()) {
532 return OK;
534 QString recentDirClass;
535 const QUrl saveUrl = QFileDialog::getSaveFileUrl(parentWidget()
536 , KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///OpenMessage")), recentDirClass).toLocalFile()
537 , mUrl.fileName());
538 if (saveUrl.isEmpty()) {
539 return Canceled;
542 if (!recentDirClass.isEmpty()) {
543 KRecentDirs::add(recentDirClass, saveUrl.path());
546 bool fileExists = false;
547 if (saveUrl.isLocalFile()) {
548 fileExists = QFile::exists(saveUrl.toLocalFile());
549 } else {
550 auto job = KIO::stat(saveUrl, KIO::StatJob::DestinationSide, 0);
551 KJobWidgets::setWindow(job, parentWidget());
552 fileExists = job->exec();
555 if (fileExists) {
556 if (KMessageBox::warningContinueCancel(Q_NULLPTR,
557 xi18nc("@info", "File <filename>%1</filename> exists.<nl/>Do you want to replace it?",
558 saveUrl.toDisplayString()), i18n("Save to File"), KGuiItem(i18n("&Replace")))
559 != KMessageBox::Continue) {
560 return Canceled;
563 KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite);
564 connect(job, &KIO::Job::result, this, &KMUrlSaveCommand::slotUrlSaveResult);
565 setEmitsCompletedItself(true);
566 return OK;
569 void KMUrlSaveCommand::slotUrlSaveResult(KJob *job)
571 if (job->error()) {
572 showJobError(job);
573 setResult(Failed);
574 } else {
575 setResult(OK);
577 Q_EMIT completed(this);
580 KMEditMessageCommand::KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg)
581 : KMCommand(parent), mMessage(msg)
585 KMCommand::Result KMEditMessageCommand::execute()
587 if (!mMessage) {
588 return Failed;
591 KMail::Composer *win = KMail::makeComposer();
592 bool lastEncrypt = false;
593 bool lastSign = false;
594 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage);
595 win->setMessage(mMessage, lastSign, lastEncrypt, false, true);
596 win->show();
597 win->setModified(true);
598 return OK;
601 KMEditItemCommand::KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource)
602 : KMCommand(parent, msg)
603 , mDeleteFromSource(deleteFromSource)
605 fetchScope().fetchFullPayload(true);
606 fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
607 fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
608 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
611 KMEditItemCommand::~KMEditItemCommand()
615 KMCommand::Result KMEditItemCommand::execute()
617 Akonadi::Item item = retrievedMessage();
618 if (!item.isValid() || !item.parentCollection().isValid()) {
619 return Failed;
621 KMime::Message::Ptr msg = MessageCore::Util::message(item);
622 if (!msg) {
623 return Failed;
626 if (mDeleteFromSource) {
627 setDeletesItself(true);
628 Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(item);
629 connect(job, &KIO::Job::result, this, &KMEditItemCommand::slotDeleteItem);
631 KMail::Composer *win = KMail::makeComposer();
632 bool lastEncrypt = false;
633 bool lastSign = false;
634 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
635 win->setMessage(msg, lastSign, lastEncrypt, false, true);
637 win->setFolder(item.parentCollection());
639 const MailTransport::TransportAttribute *transportAttribute = item.attribute<MailTransport::TransportAttribute>();
640 if (transportAttribute) {
641 win->setCurrentTransport(transportAttribute->transportId());
642 } else {
643 int transportId = msg->headerByType("X-KMail-Transport") ? msg->headerByType("X-KMail-Transport")->asUnicodeString().toInt() : -1;
644 if (transportId != -1) {
645 win->setCurrentTransport(transportId);
649 if (auto hdr = msg->replyTo(false)) {
650 const QString replyTo = hdr->asUnicodeString();
651 win->setCurrentReplyTo(replyTo);
654 const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute<MailTransport::SentBehaviourAttribute>();
655 if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) {
656 win->setFcc(QString::number(sentAttribute->moveToCollection().id()));
658 win->show();
659 if (mDeleteFromSource) {
660 win->setModified(true);
663 return OK;
666 void KMEditItemCommand::slotDeleteItem(KJob *job)
668 if (job->error()) {
669 showJobError(job);
670 setResult(Failed);
671 } else {
672 setResult(OK);
674 Q_EMIT completed(this);
675 deleteLater();
678 KMUseTemplateCommand::KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg)
679 : KMCommand(parent, msg)
681 fetchScope().fetchFullPayload(true);
682 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
685 KMCommand::Result KMUseTemplateCommand::execute()
687 Akonadi::Item item = retrievedMessage();
688 if (!item.isValid()
689 || !item.parentCollection().isValid() ||
690 !CommonKernel->folderIsTemplates(item.parentCollection())
692 return Failed;
694 KMime::Message::Ptr msg = MessageCore::Util::message(item);
695 if (!msg) {
696 return Failed;
699 KMime::Message::Ptr newMsg(new KMime::Message);
700 newMsg->setContent(msg->encodedContent());
701 newMsg->parse();
702 // these fields need to be regenerated for the new message
703 newMsg->removeHeader<KMime::Headers::Date>();
704 newMsg->removeHeader<KMime::Headers::MessageID>();
706 KMail::Composer *win = KMail::makeComposer();
708 win->setMessage(newMsg, false, false, false, true);
709 win->show();
710 return OK;
713 KMSaveMsgCommand::KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList)
714 : KMCommand(parent, msgList)
716 if (msgList.empty()) {
717 return;
720 fetchScope().fetchFullPayload(true); // ### unless we call the corresponding KMCommand ctor, this has no effect
723 KMCommand::Result KMSaveMsgCommand::execute()
725 if (!MessageViewer::Util::saveMessageInMbox(retrievedMsgs(), parentWidget())) {
726 return Failed;
728 return OK;
731 //-----------------------------------------------------------------------------
733 KMOpenMsgCommand::KMOpenMsgCommand(QWidget *parent, const QUrl &url,
734 const QString &encoding, KMMainWidget *main)
735 : KMCommand(parent),
736 mUrl(url),
737 mJob(Q_NULLPTR),
738 mEncoding(encoding),
739 mMainWidget(main)
741 qCDebug(KMAIL_LOG) << "url :" << url;
744 KMCommand::Result KMOpenMsgCommand::execute()
746 if (mUrl.isEmpty()) {
747 mUrl = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Open Message"), QUrl(),
748 i18n("Message (*.mbox)")
751 if (mUrl.isEmpty()) {
752 return Canceled;
755 if (mMainWidget) {
756 mMainWidget->addRecentFile(mUrl);
759 setDeletesItself(true);
760 mJob = KIO::get(mUrl, KIO::NoReload, KIO::HideProgressInfo);
761 connect(mJob, &KIO::TransferJob::data,
762 this, &KMOpenMsgCommand::slotDataArrived);
763 connect(mJob, &KJob::result,
764 this, &KMOpenMsgCommand::slotResult);
765 setEmitsCompletedItself(true);
766 return OK;
769 void KMOpenMsgCommand::slotDataArrived(KIO::Job *, const QByteArray &data)
771 if (data.isEmpty()) {
772 return;
775 mMsgString.append(QString::fromLatin1(data.data()));
778 void KMOpenMsgCommand::doesNotContainMessage()
780 KMessageBox::sorry(parentWidget(),
781 i18n("The file does not contain a message."));
782 setResult(Failed);
783 Q_EMIT completed(this);
784 // Emulate closing of a secondary window so that KMail exits in case it
785 // was started with the --view command line option. Otherwise an
786 // invisible KMail would keep running.
787 SecondaryWindow *win = new SecondaryWindow();
788 win->close();
789 win->deleteLater();
790 deleteLater();
794 void KMOpenMsgCommand::slotResult(KJob *job)
796 if (job->error()) {
797 // handle errors
798 showJobError(job);
799 setResult(Failed);
800 } else {
801 if (mMsgString.isEmpty()) {
802 qCDebug(KMAIL_LOG) << " Message not found. There is a problem";
803 doesNotContainMessage();
804 return;
806 int startOfMessage = 0;
807 if (mMsgString.startsWith(QStringLiteral("From "))) {
808 startOfMessage = mMsgString.indexOf(QLatin1Char('\n'));
809 if (startOfMessage == -1) {
810 doesNotContainMessage();
811 return;
813 startOfMessage += 1; // the message starts after the '\n'
815 // check for multiple messages in the file
816 bool multipleMessages = true;
817 int endOfMessage = mMsgString.indexOf(QLatin1String("\nFrom "));
818 if (endOfMessage == -1) {
819 endOfMessage = mMsgString.length();
820 multipleMessages = false;
822 KMime::Message *msg = new KMime::Message;
823 msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage).toUtf8()));
824 msg->parse();
825 if (!msg->hasContent()) {
826 delete msg; msg = Q_NULLPTR;
827 doesNotContainMessage();
828 return;
830 KMReaderMainWin *win = new KMReaderMainWin();
831 KMime::Message::Ptr mMsg(msg);
832 win->showMessage(mEncoding, mMsg);
833 win->show();
834 if (multipleMessages)
835 KMessageBox::information(win,
836 i18n("The file contains multiple messages. "
837 "Only the first message is shown."));
838 setResult(OK);
840 Q_EMIT completed(this);
841 deleteLater();
844 //-----------------------------------------------------------------------------
845 KMReplyCommand::KMReplyCommand(QWidget *parent, const Akonadi::Item &msg, MessageComposer::ReplyStrategy replyStrategy,
846 const QString &selection, bool noquote, const QString &templateName)
847 : KMCommand(parent, msg),
848 mSelection(selection),
849 mTemplate(templateName),
850 m_replyStrategy(replyStrategy),
851 mNoQuote(noquote)
854 if (!noquote) {
855 fetchScope().fetchFullPayload(true);
858 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
861 KMCommand::Result KMReplyCommand::execute()
863 #ifndef QT_NO_CURSOR
864 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
865 #endif
866 Akonadi::Item item = retrievedMessage();
867 KMime::Message::Ptr msg = MessageCore::Util::message(item);
868 if (!msg) {
869 return Failed;
872 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
873 factory.setIdentityManager(KMKernel::self()->identityManager());
874 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
875 factory.setMailingListAddresses(KMail::Util::mailingListsFromMessage(item));
876 factory.putRepliesInSameFolder(KMail::Util::putRepliesInSameFolder(item));
877 factory.setReplyStrategy(m_replyStrategy);
878 factory.setSelection(mSelection);
879 if (!mTemplate.isEmpty()) {
880 factory.setTemplate(mTemplate);
882 if (mNoQuote) {
883 factory.setQuote(false);
885 bool lastEncrypt = false;
886 bool lastSign = false;
887 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
889 MessageFactory::MessageReply reply = factory.createReply();
890 KMail::Composer *win = KMail::makeComposer(KMime::Message::Ptr(reply.msg), lastSign, lastEncrypt, replyContext(reply), 0,
891 mSelection, mTemplate);
892 win->setFocusToEditor();
893 win->show();
895 return OK;
898 KMForwardCommand::KMForwardCommand(QWidget *parent,
899 const Akonadi::Item::List &msgList, uint identity, const QString &templateName)
900 : KMCommand(parent, msgList),
901 mIdentity(identity),
902 mTemplate(templateName)
904 fetchScope().fetchFullPayload(true);
905 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
908 KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item &msg,
909 uint identity, const QString &templateName)
910 : KMCommand(parent, msg),
911 mIdentity(identity),
912 mTemplate(templateName)
914 fetchScope().fetchFullPayload(true);
915 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
918 KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item &item)
920 KMime::Message::Ptr msg = MessageCore::Util::message(item);
921 if (!msg) {
922 return Failed;
924 #ifndef QT_NO_CURSOR
925 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
926 #endif
927 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
928 factory.setIdentityManager(KMKernel::self()->identityManager());
929 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
930 if (!mTemplate.isEmpty()) {
931 factory.setTemplate(mTemplate);
933 KMime::Message::Ptr fwdMsg = factory.createForward();
935 uint id = msg->headerByType("X-KMail-Identity") ? msg->headerByType("X-KMail-Identity")->asUnicodeString().trimmed().toUInt() : 0;
936 qCDebug(KMAIL_LOG) << "mail" << msg->encodedContent();
937 bool lastEncrypt = false;
938 bool lastSign = false;
939 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
941 if (id == 0) {
942 id = mIdentity;
945 KMail::Composer *win = KMail::makeComposer(fwdMsg, lastSign, lastEncrypt, KMail::Composer::Forward, id, QString(), mTemplate);
946 win->show();
948 return OK;
951 KMCommand::Result KMForwardCommand::execute()
953 Akonadi::Item::List msgList = retrievedMsgs();
955 if (msgList.count() >= 2) {
956 // ask if they want a mime digest forward
958 int answer = KMessageBox::questionYesNoCancel(
959 parentWidget(),
960 i18n("Do you want to forward the selected messages as "
961 "attachments in one message (as a MIME digest) or as "
962 "individual messages?"), QString(),
963 KGuiItem(i18n("Send As Digest")),
964 KGuiItem(i18n("Send Individually")));
966 if (answer == KMessageBox::Yes) {
967 Akonadi::Item firstItem(msgList.first());
968 MessageFactory factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), MailCommon::Util::updatedCollection(firstItem.parentCollection()));
969 factory.setIdentityManager(KMKernel::self()->identityManager());
970 factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem));
972 QPair< KMime::Message::Ptr, KMime::Content * > fwdMsg = factory.createForwardDigestMIME(msgList);
973 KMail::Composer *win = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity);
974 win->addAttach(fwdMsg.second);
975 win->show();
976 return OK;
977 } else if (answer == KMessageBox::No) { // NO MIME DIGEST, Multiple forward
978 Akonadi::Item::List::const_iterator it;
979 Akonadi::Item::List::const_iterator end(msgList.constEnd());
981 for (it = msgList.constBegin(); it != end; ++it) {
982 if (createComposer(*it) == Failed) {
983 return Failed;
986 return OK;
987 } else {
988 // user cancelled
989 return OK;
993 // forward a single message at most.
994 Akonadi::Item item = msgList.first();
995 if (createComposer(item) == Failed) {
996 return Failed;
998 return OK;
1001 KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent,
1002 const Akonadi::Item::List &msgList, uint identity, KMail::Composer *win)
1003 : KMCommand(parent, msgList), mIdentity(identity),
1004 mWin(QPointer<KMail::Composer>(win))
1006 fetchScope().fetchFullPayload(true);
1007 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1010 KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent,
1011 const Akonadi::Item &msg, uint identity, KMail::Composer *win)
1012 : KMCommand(parent, msg), mIdentity(identity),
1013 mWin(QPointer< KMail::Composer >(win))
1015 fetchScope().fetchFullPayload(true);
1016 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1019 KMCommand::Result KMForwardAttachedCommand::execute()
1021 Akonadi::Item::List msgList = retrievedMsgs();
1022 Akonadi::Item firstItem(msgList.first());
1023 MessageFactory factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), MailCommon::Util::updatedCollection(firstItem.parentCollection()));
1024 factory.setIdentityManager(KMKernel::self()->identityManager());
1025 factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem));
1027 QPair< KMime::Message::Ptr, QList< KMime::Content * > > fwdMsg = factory.createAttachedForward(msgList);
1028 if (!mWin) {
1029 mWin = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity);
1031 foreach (KMime::Content *attach, fwdMsg.second) {
1032 mWin->addAttach(attach);
1034 mWin->show();
1035 return OK;
1038 KMRedirectCommand::KMRedirectCommand(QWidget *parent,
1039 const Akonadi::Item::List &msgList)
1040 : KMCommand(parent, msgList)
1042 fetchScope().fetchFullPayload(true);
1043 fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
1044 fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
1046 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1049 KMRedirectCommand::KMRedirectCommand(QWidget *parent,
1050 const Akonadi::Item &msg)
1051 : KMCommand(parent, msg)
1053 fetchScope().fetchFullPayload(true);
1054 fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
1055 fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
1057 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1060 KMCommand::Result KMRedirectCommand::execute()
1062 const MailCommon::RedirectDialog::SendMode sendMode = MessageComposer::MessageComposerSettings::self()->sendImmediate()
1063 ? MailCommon::RedirectDialog::SendNow
1064 : MailCommon::RedirectDialog::SendLater;
1066 QScopedPointer<MailCommon::RedirectDialog> dlg(new MailCommon::RedirectDialog(sendMode, parentWidget()));
1067 dlg->setObjectName(QStringLiteral("redirect"));
1068 if (dlg->exec() == QDialog::Rejected || !dlg) {
1069 return Failed;
1071 if (!TransportManager::self()->showTransportCreationDialog(parentWidget(), TransportManager::IfNoTransportExists)) {
1072 return Failed;
1075 //TODO use sendlateragent here too.
1076 const MessageComposer::MessageSender::SendMethod method = (dlg->sendMode() == MailCommon::RedirectDialog::SendNow)
1077 ? MessageComposer::MessageSender::SendImmediate
1078 : MessageComposer::MessageSender::SendLater;
1080 const int identity = dlg->identity();
1081 int transportId = dlg->transportId();
1082 const QString to = dlg->to();
1083 const QString cc = dlg->cc();
1084 const QString bcc = dlg->bcc();
1085 foreach (const Akonadi::Item &item, retrievedMsgs()) {
1086 const KMime::Message::Ptr msg = MessageCore::Util::message(item);
1087 if (!msg) {
1088 return Failed;
1091 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
1092 factory.setIdentityManager(KMKernel::self()->identityManager());
1093 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1095 if (transportId == -1) {
1096 const MailTransport::TransportAttribute *transportAttribute = item.attribute<MailTransport::TransportAttribute>();
1097 if (transportAttribute) {
1098 transportId = transportAttribute->transportId();
1099 const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId);
1100 if (!transport) {
1101 transportId = -1;
1106 const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute<MailTransport::SentBehaviourAttribute>();
1107 QString fcc;
1108 if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) {
1109 fcc = QString::number(sentAttribute->moveToCollection().id());
1112 const KMime::Message::Ptr newMsg = factory.createRedirect(to, cc, bcc, transportId, fcc, identity);
1113 if (!newMsg) {
1114 return Failed;
1117 MessageStatus status;
1118 status.setStatusFromFlags(item.flags());
1119 if (!status.isRead()) {
1120 FilterAction::sendMDN(item, KMime::MDN::Dispatched);
1123 if (!kmkernel->msgSender()->send(newMsg, method)) {
1124 qCDebug(KMAIL_LOG) << "KMRedirectCommand: could not redirect message (sending failed)";
1125 return Failed; // error: couldn't send
1129 return OK;
1132 KMPrintCommand::KMPrintCommand(QWidget *parent, const Akonadi::Item &msg,
1133 MessageViewer::HeaderStylePlugin *plugin,
1134 MessageViewer::Viewer::DisplayFormatMessage format, bool htmlLoadExtOverride,
1135 bool useFixedFont, const QString &encoding)
1136 : KMCommand(parent, msg),
1137 mHeaderStylePlugin(plugin),
1138 mAttachmentStrategy(Q_NULLPTR),
1139 mEncoding(encoding),
1140 mFormat(format),
1141 mHtmlLoadExtOverride(htmlLoadExtOverride),
1142 mUseFixedFont(useFixedFont),
1143 mPrintPreview(false)
1145 fetchScope().fetchFullPayload(true);
1146 if (MessageCore::MessageCoreSettings::useDefaultFonts()) {
1147 mOverrideFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1148 } else {
1149 mOverrideFont = MessageViewer::MessageViewerSettings::self()->printFont();
1153 void KMPrintCommand::setOverrideFont(const QFont &font)
1155 mOverrideFont = font;
1158 void KMPrintCommand::setAttachmentStrategy(const MessageViewer::AttachmentStrategy *strategy)
1160 mAttachmentStrategy = strategy;
1163 void KMPrintCommand::setPrintPreview(bool preview)
1165 mPrintPreview = preview;
1168 KMCommand::Result KMPrintCommand::execute()
1170 // the window will be deleted after printout is performed, in KMReaderWin::slotPrintMsg()
1171 KMReaderWin *printerWin = new KMReaderWin(Q_NULLPTR, kmkernel->mainWin(), Q_NULLPTR, Q_NULLPTR);
1172 printerWin->setPrinting(true);
1173 printerWin->readConfig();
1174 if (mHeaderStylePlugin) {
1175 printerWin->viewer()->setPluginName(mHeaderStylePlugin->name());
1177 printerWin->setDisplayFormatMessageOverwrite(mFormat);
1178 printerWin->setHtmlLoadExtOverride(mHtmlLoadExtOverride);
1179 printerWin->setUseFixedFont(mUseFixedFont);
1180 printerWin->setOverrideEncoding(mEncoding);
1181 printerWin->cssHelper()->setPrintFont(mOverrideFont);
1182 printerWin->setDecryptMessageOverwrite(true);
1183 if (mAttachmentStrategy != Q_NULLPTR) {
1184 printerWin->setAttachmentStrategy(mAttachmentStrategy);
1186 if (mPrintPreview) {
1187 printerWin->viewer()->printPreviewMessage(retrievedMessage());
1188 } else {
1189 printerWin->viewer()->printMessage(retrievedMessage());
1191 return OK;
1194 KMSetStatusCommand::KMSetStatusCommand(const MessageStatus &status,
1195 const Akonadi::Item::List &items, bool invert)
1196 : KMCommand(Q_NULLPTR, items), mStatus(status), mInvertMark(invert)
1198 setDeletesItself(true);
1201 KMCommand::Result KMSetStatusCommand::execute()
1203 bool parentStatus = false;
1204 // Toggle actions on threads toggle the whole thread
1205 // depending on the state of the parent.
1206 if (mInvertMark) {
1207 const Akonadi::Item first = retrievedMsgs().first();
1208 MessageStatus pStatus;
1209 pStatus.setStatusFromFlags(first.flags());
1210 if (pStatus & mStatus) {
1211 parentStatus = true;
1212 } else {
1213 parentStatus = false;
1217 Akonadi::Item::List itemsToModify;
1218 foreach (const Akonadi::Item &it, retrievedMsgs()) {
1219 if (mInvertMark) {
1220 //qCDebug(KMAIL_LOG)<<" item ::"<<tmpItem;
1221 if (it.isValid()) {
1222 bool myStatus;
1223 MessageStatus itemStatus;
1224 itemStatus.setStatusFromFlags(it.flags());
1225 if (itemStatus & mStatus) {
1226 myStatus = true;
1227 } else {
1228 myStatus = false;
1230 if (myStatus != parentStatus) {
1231 continue;
1235 Akonadi::Item item(it);
1236 const Akonadi::Item::Flag flag = *(mStatus.statusFlags().begin());
1237 if (mInvertMark) {
1238 if (item.hasFlag(flag)) {
1239 item.clearFlag(flag);
1240 itemsToModify.push_back(item);
1241 } else {
1242 item.setFlag(flag);
1243 itemsToModify.push_back(item);
1245 } else {
1246 if (!item.hasFlag(flag)) {
1247 item.setFlag(flag);
1248 itemsToModify.push_back(item);
1253 if (itemsToModify.isEmpty()) {
1254 slotModifyItemDone(Q_NULLPTR); // pretend we did something
1255 } else {
1256 Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this);
1257 modifyJob->disableRevisionCheck();
1258 modifyJob->setIgnorePayload(true);
1259 connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetStatusCommand::slotModifyItemDone);
1261 return OK;
1264 void KMSetStatusCommand::slotModifyItemDone(KJob *job)
1266 if (job && job->error()) {
1267 qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText();
1269 deleteLater();
1272 KMSetTagCommand::KMSetTagCommand(const Akonadi::Tag::List &tags, const Akonadi::Item::List &item,
1273 SetTagMode mode)
1274 : mTags(tags)
1275 , mItem(item)
1276 , mMode(mode)
1278 setDeletesItself(true);
1281 KMCommand::Result KMSetTagCommand::execute()
1283 Q_FOREACH (const Akonadi::Tag &tag, mTags) {
1284 if (!tag.isValid()) {
1285 Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(tag, this);
1286 connect(createJob, &Akonadi::TagCreateJob::result, this, &KMSetTagCommand::slotModifyItemDone);
1287 } else {
1288 mCreatedTags << tag;
1292 if (mCreatedTags.size() == mTags.size()) {
1293 setTags();
1294 } else {
1295 deleteLater();
1298 return OK;
1301 void KMSetTagCommand::setTags()
1303 Akonadi::Item::List itemsToModify;
1304 itemsToModify.reserve(mItem.count());
1305 Q_FOREACH (const Akonadi::Item &i, mItem) {
1306 Akonadi::Item item(i);
1307 if (mMode == CleanExistingAndAddNew) {
1308 //WorkAround. ClearTags doesn't work.
1309 Q_FOREACH (const Akonadi::Tag &tag, item.tags()) {
1310 item.clearTag(tag);
1312 //item.clearTags();
1315 if (mMode == KMSetTagCommand::Toggle) {
1316 Q_FOREACH (const Akonadi::Tag &tag, mCreatedTags) {
1317 if (item.hasTag(tag)) {
1318 item.clearTag(tag);
1319 } else {
1320 item.setTag(tag);
1323 } else {
1324 if (!mCreatedTags.isEmpty()) {
1325 item.setTags(mCreatedTags);
1328 itemsToModify << item;
1330 Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this);
1331 modifyJob->disableRevisionCheck();
1332 modifyJob->setIgnorePayload(true);
1333 connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetTagCommand::slotModifyItemDone);
1335 if (!mCreatedTags.isEmpty()) {
1336 KConfigGroup tag(KMKernel::self()->config(), "MessageListView");
1337 const QString oldTagList = tag.readEntry("TagSelected");
1338 QStringList lst = oldTagList.split(QStringLiteral(","));
1339 Q_FOREACH (const Akonadi::Tag &tag, mCreatedTags) {
1340 const QString url = tag.url().url();
1341 if (!lst.contains(url)) {
1342 lst.append(url);
1345 tag.writeEntry("TagSelected", lst);
1346 KMKernel::self()->updatePaneTagComboBox();
1350 void KMSetTagCommand::slotModifyItemDone(KJob *job)
1352 if (job && job->error()) {
1353 qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText();
1355 deleteLater();
1358 KMFilterActionCommand::KMFilterActionCommand(QWidget *parent,
1359 const QVector<qlonglong> &msgListId,
1360 const QString &filterId)
1361 : KMCommand(parent), mMsgListId(msgListId), mFilterId(filterId)
1365 KMCommand::Result KMFilterActionCommand::execute()
1367 #ifndef QT_NO_CURSOR
1368 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
1369 #endif
1370 int msgCount = 0;
1371 const int msgCountToFilter = mMsgListId.count();
1372 ProgressItem *progressItem =
1373 ProgressManager::createProgressItem(
1374 QLatin1String("filter") + ProgressManager::getUniqueID(),
1375 i18n("Filtering messages"), QString(), true, KPIM::ProgressItem::Unknown);
1376 progressItem->setTotalItems(msgCountToFilter);
1378 foreach (const qlonglong &id, mMsgListId) {
1379 int diff = msgCountToFilter - ++msgCount;
1380 if (diff < 10 || !(msgCount % 10) || msgCount <= 10) {
1381 progressItem->updateProgress();
1382 const QString statusMsg = i18n("Filtering message %1 of %2",
1383 msgCount, msgCountToFilter);
1384 KPIM::BroadcastStatus::instance()->setStatusMsg(statusMsg);
1385 qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 50);
1388 MailCommon::FilterManager::instance()->filter(Akonadi::Item(id), mFilterId, QString());
1389 progressItem->incCompletedItems();
1392 progressItem->setComplete();
1393 progressItem = Q_NULLPTR;
1394 return OK;
1397 KMMetaFilterActionCommand::KMMetaFilterActionCommand(const QString &filterId,
1398 KMMainWidget *main)
1399 : QObject(main),
1400 mFilterId(filterId), mMainWidget(main)
1404 void KMMetaFilterActionCommand::start()
1406 KMCommand *filterCommand = new KMFilterActionCommand(
1407 mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId(), mFilterId);
1408 filterCommand->start();
1411 KMMailingListFilterCommand::KMMailingListFilterCommand(QWidget *parent,
1412 const Akonadi::Item &msg)
1413 : KMCommand(parent, msg)
1417 KMCommand::Result KMMailingListFilterCommand::execute()
1419 QByteArray name;
1420 QString value;
1421 Akonadi::Item item = retrievedMessage();
1422 KMime::Message::Ptr msg = MessageCore::Util::message(item);
1423 if (!msg) {
1424 return Failed;
1426 if (!MailingList::name(msg, name, value).isEmpty()) {
1427 FilterIf->openFilterDialog(false);
1428 FilterIf->createFilter(name, value);
1429 return OK;
1430 } else {
1431 return Failed;
1435 KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder,
1436 const Akonadi::Item::List &msgList)
1437 : KMCommand(Q_NULLPTR, msgList), mDestFolder(destFolder)
1441 KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg)
1442 : KMCommand(Q_NULLPTR, msg), mDestFolder(destFolder)
1446 KMCommand::Result KMCopyCommand::execute()
1448 setDeletesItself(true);
1450 Akonadi::Item::List listItem = retrievedMsgs();
1451 Akonadi::ItemCopyJob *job = new Akonadi::ItemCopyJob(listItem, Akonadi::Collection(mDestFolder.id()), this);
1452 connect(job, &KIO::Job::result, this, &KMCopyCommand::slotCopyResult);
1454 return OK;
1457 void KMCopyCommand::slotCopyResult(KJob *job)
1459 if (job->error()) {
1460 // handle errors
1461 showJobError(job);
1462 setResult(Failed);
1464 Q_EMIT completed(this);
1465 deleteLater();
1468 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder,
1469 const Akonadi::Item::List &msgList,
1470 MessageList::Core::MessageItemSetReference ref)
1471 : KMCommand(Q_NULLPTR, msgList), mDestFolder(destFolder), mProgressItem(Q_NULLPTR), mRef(ref)
1473 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1476 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder,
1477 const Akonadi::Item &msg,
1478 MessageList::Core::MessageItemSetReference ref)
1479 : KMCommand(Q_NULLPTR, msg), mDestFolder(destFolder), mProgressItem(Q_NULLPTR), mRef(ref)
1481 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1484 void KMMoveCommand::slotMoveResult(KJob *job)
1486 if (job->error()) {
1487 // handle errors
1488 showJobError(job);
1489 completeMove(Failed);
1490 } else {
1491 completeMove(OK);
1495 KMCommand::Result KMMoveCommand::execute()
1497 #ifndef QT_NO_CURSOR
1498 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
1499 #endif
1500 setEmitsCompletedItself(true);
1501 setDeletesItself(true);
1502 Akonadi::Item::List retrievedList = retrievedMsgs();
1503 if (!retrievedList.isEmpty()) {
1504 if (mDestFolder.isValid()) {
1505 Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(retrievedList, mDestFolder, this);
1506 connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1508 // group by source folder for undo
1509 std::sort(retrievedList.begin(), retrievedList.end(), boost::bind(&Akonadi::Item::storageCollectionId, _1) <
1510 boost::bind(&Akonadi::Item::storageCollectionId, _2));
1511 Akonadi::Collection parent;
1512 int undoId = -1;
1513 foreach (const Akonadi::Item &item, retrievedList) {
1514 if (item.storageCollectionId() <= 0) {
1515 continue;
1517 if (parent.id() != item.storageCollectionId()) {
1518 parent = Akonadi::Collection(item.storageCollectionId());
1519 undoId = kmkernel->undoStack()->newUndoAction(parent, mDestFolder);
1521 kmkernel->undoStack()->addMsgToAction(undoId, item);
1523 } else {
1524 Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(retrievedList, this);
1525 connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1527 } else {
1528 deleteLater();
1529 return Failed;
1531 // TODO set SSL state according to source and destfolder connection?
1532 Q_ASSERT(!mProgressItem);
1533 mProgressItem =
1534 ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(),
1535 mDestFolder.isValid() ? i18n("Moving messages") : i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown);
1536 mProgressItem->setUsesBusyIndicator(true);
1537 connect(mProgressItem, &ProgressItem::progressItemCanceled,
1538 this, &KMMoveCommand::slotMoveCanceled);
1539 return OK;
1542 void KMMoveCommand::completeMove(Result result)
1544 if (mProgressItem) {
1545 mProgressItem->setComplete();
1546 mProgressItem = Q_NULLPTR;
1548 setResult(result);
1549 Q_EMIT moveDone(this);
1550 Q_EMIT completed(this);
1551 deleteLater();
1554 void KMMoveCommand::slotMoveCanceled()
1556 completeMove(Canceled);
1559 // srcFolder doesn't make much sense for searchFolders
1560 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder,
1561 const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref)
1562 : KMMoveCommand(findTrashFolder(srcFolder), msgList, ref)
1566 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref)
1567 : KMMoveCommand(findTrashFolder(srcFolder), msg, ref)
1571 Akonadi::Collection KMTrashMsgCommand::findTrashFolder(const Akonadi::Collection &folder)
1573 Akonadi::Collection col = CommonKernel->trashCollectionFromResource(folder);
1574 if (!col.isValid()) {
1575 col = CommonKernel->trashCollectionFolder();
1577 if (folder != col) {
1578 return col;
1580 return Akonadi::Collection();
1583 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer)
1584 : KMCommand(parent, msg),
1585 mViewer(viewer)
1587 fetchScope().fetchFullPayload(true);
1590 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs)
1591 : KMCommand(parent, msgs),
1592 mViewer(Q_NULLPTR)
1594 fetchScope().fetchFullPayload(true);
1597 KMCommand::Result KMSaveAttachmentsCommand::execute()
1599 KMime::Content::List contentsToSave;
1600 foreach (const Akonadi::Item &item, retrievedMsgs()) {
1601 if (item.hasPayload<KMime::Message::Ptr>()) {
1602 contentsToSave += item.payload<KMime::Message::Ptr>()->attachments();
1603 } else {
1604 qCWarning(KMAIL_LOG) << "Retrieved item has no payload? Ignoring for saving the attachments";
1607 QUrl currentUrl;
1608 if (MessageViewer::Util::saveAttachments(contentsToSave, parentWidget(), currentUrl)) {
1609 if (mViewer) {
1610 mViewer->showOpenAttachmentFolderWidget(currentUrl);
1612 return OK;
1614 return Failed;
1617 KMResendMessageCommand::KMResendMessageCommand(QWidget *parent,
1618 const Akonadi::Item &msg)
1619 : KMCommand(parent, msg)
1621 fetchScope().fetchFullPayload(true);
1622 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1625 KMCommand::Result KMResendMessageCommand::execute()
1627 Akonadi::Item item = retrievedMessage();
1628 KMime::Message::Ptr msg = MessageCore::Util::message(item);
1629 if (!msg) {
1630 return Failed;
1633 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
1634 factory.setIdentityManager(KMKernel::self()->identityManager());
1635 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1636 KMime::Message::Ptr newMsg = factory.createResend();
1637 newMsg->contentType()->setCharset(MessageViewer::NodeHelper::charset(msg.data()));
1639 KMail::Composer *win = KMail::makeComposer();
1640 if (auto hdr = msg->replyTo(false)) {
1641 const QString replyTo = hdr->asUnicodeString();
1642 win->setCurrentReplyTo(replyTo);
1644 bool lastEncrypt = false;
1645 bool lastSign = false;
1646 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
1647 win->setMessage(newMsg, lastSign, lastEncrypt, false, true);
1649 win->show();
1651 return OK;
1654 KMShareImageCommand::KMShareImageCommand(const QUrl &url, QWidget *parent)
1655 : KMCommand(parent),
1656 mUrl(url)
1660 KMCommand::Result KMShareImageCommand::execute()
1662 KMime::Message::Ptr msg(new KMime::Message);
1663 uint id = 0;
1665 MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
1666 msg->contentType()->setCharset("utf-8");
1668 KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
1669 win->setFocusToSubject();
1670 win->addAttachment(mUrl, i18n("Image"));
1671 win->show();
1672 return OK;
1675 KMFetchMessageCommand::KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item)
1676 : KMCommand(parent, item)
1678 // Workaround KMCommand::transferSelectedMsgs() expecting non-empty fetchscope
1679 fetchScope().fetchFullPayload(true);
1682 Akonadi::ItemFetchJob *KMFetchMessageCommand::createFetchJob(const Akonadi::Item::List &items)
1684 Q_ASSERT(items.size() == 1);
1685 Akonadi::ItemFetchJob *fetch = MessageViewer::Viewer::createFetchJob(items.first());
1686 fetchScope() = fetch->fetchScope();
1687 return fetch;
1690 KMCommand::Result KMFetchMessageCommand::execute()
1692 Akonadi::Item item = retrievedMessage();
1693 if (!item.isValid() || !item.hasPayload<KMime::Message::Ptr>()) {
1694 return Failed;
1697 mItem = item;
1698 return OK;
1701 Akonadi::Item KMFetchMessageCommand::item() const
1703 return mItem;