Use qtpaths directly
[kdepim.git] / kmail / kmcommands.cpp
blob114586155aca97a34e22d3b9fb5d406cc920a3e4
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-2015 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 "mailcommon/mailkernel.h"
48 #include "mailcommon/mailutil.h"
49 #include "messageviewer/headerstyleplugin.h"
51 #include <unistd.h> // link()
52 #include <QProgressDialog>
53 #include <KEmailAddress>
54 #include <kdbusservicestarter.h>
55 #include "kmail_debug.h"
56 #include <qfiledialog.h>
57 #include <KLocalizedString>
58 #include <kmessagebox.h>
59 #include <kbookmarkmanager.h>
60 #include <QFileDialog>
61 #include <KJobWidgets>
62 // KIO headers
63 #include <kio/job.h>
64 #include <kio/jobuidelegate.h>
65 #include <kio/statjob.h>
67 #include <kmime/kmime_message.h>
69 #include <KIdentityManagement/kidentitymanagement/identitymanager.h>
70 #include <AkonadiCore/itemmodifyjob.h>
71 #include <AkonadiCore/itemfetchjob.h>
72 #include <MailCommon/MDNStateAttribute>
74 #include "MailCommon/FolderCollection"
76 #include "MessageCore/MailingList"
77 #include "editor/composer.h"
78 #include "MailCommon/FilterAction"
79 #include "MailCommon/FilterManager"
80 #include "MailCommon/MailFilter"
81 #include "MailCommon/RedirectDialog"
82 #include "kmmainwidget.h"
83 #include "undostack.h"
84 #ifndef QT_NO_CURSOR
85 #include "Libkdepim/KCursorSaver"
86 #endif
87 #include "MessageViewer/ObjectTreeParser"
88 #include "MessageViewer/CSSHelper"
89 #include "messagecomposer/util.h"
90 #include "kmreadermainwin.h"
91 #include "secondarywindow.h"
92 using KMail::SecondaryWindow;
93 #include "util.h"
94 #include "libkdepim/broadcaststatus.h"
95 #include "settings/kmailsettings.h"
96 #include "MessageCore/StringUtil"
98 #include "messageviewer/messageviewersettings.h"
99 #include "MessageCore/MessageCoreSettings"
101 #include <AkonadiCore/itemmovejob.h>
102 #include <AkonadiCore/itemcopyjob.h>
103 #include <AkonadiCore/itemdeletejob.h>
104 #include <AkonadiCore/tag.h>
105 #include <AkonadiCore/tagcreatejob.h>
106 #include <MailTransport/mailtransport/transportattribute.h>
107 #include <MailTransport/mailtransport/sentbehaviourattribute.h>
109 #include <messagelist/pane.h>
111 #include <MailTransport/mailtransport/transportmanager.h>
112 using MailTransport::TransportManager;
114 #include "messageviewer/nodehelper.h"
115 #include "MessageViewer/ObjectTreeEmptySource"
117 #include "MessageCore/StringUtil"
118 #include "messagecore/messagehelpers.h"
120 #include "MessageComposer/MessageSender"
121 #include "MessageComposer/MessageHelper"
122 #include "MessageComposer/MessageComposerSettings"
123 #include "MessageComposer/MessageFactory"
124 using MessageComposer::MessageFactory;
126 #include "libkdepim/progressmanager.h"
127 using KPIM::ProgressManager;
128 using KPIM::ProgressItem;
129 #include <kmime/kmime_mdn.h>
130 using namespace KMime;
132 #include "Libkleo/CryptoBackend"
133 #include "Libkleo/CryptoBackendFactory"
135 #include <gpgme++/error.h>
137 #include <QByteArray>
138 #include <QApplication>
139 #include <QList>
141 #include <boost/bind.hpp>
142 #include <algorithm>
143 #include <memory>
144 #include <QStandardPaths>
145 #include <QFontDatabase>
147 using namespace MailCommon;
149 /// Small helper function to get the composer context from a reply
150 static KMail::Composer::TemplateContext replyContext(MessageFactory::MessageReply reply)
152 if (reply.replyAll) {
153 return KMail::Composer::ReplyToAll;
154 } else {
155 return KMail::Composer::Reply;
159 /// Helper to sanely show an error message for a job
160 static void showJobError(KJob *job)
162 assert(job);
163 // we can be called from the KJob::kill, where we are no longer a KIO::Job
164 // so better safe than sorry
165 KIO::Job *kiojob = dynamic_cast<KIO::Job *>(job);
166 if (kiojob && kiojob->ui()) {
167 kiojob->ui()->showErrorMessage();
168 } else {
169 qCWarning(KMAIL_LOG) << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString();
173 KMCommand::KMCommand(QWidget *parent)
174 : mCountMsgs(0), mResult(Undefined), mDeletesItself(false),
175 mEmitsCompletedItself(false), mParent(parent)
179 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item &msg)
180 : mCountMsgs(0), mResult(Undefined), mDeletesItself(false),
181 mEmitsCompletedItself(false), mParent(parent)
183 if (msg.isValid() || msg.hasPayload<KMime::Message::Ptr>()) {
184 mMsgList.append(msg);
188 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item::List &msgList)
189 : mCountMsgs(0), mResult(Undefined), mDeletesItself(false),
190 mEmitsCompletedItself(false), mParent(parent)
192 mMsgList = msgList;
195 KMCommand::~KMCommand()
199 KMCommand::Result KMCommand::result() const
201 if (mResult == Undefined) {
202 qCDebug(KMAIL_LOG) << "mResult is Undefined";
204 return mResult;
207 const Akonadi::Item::List KMCommand::retrievedMsgs() const
209 return mRetrievedMsgs;
212 Akonadi::Item KMCommand::retrievedMessage() const
214 if (mRetrievedMsgs.isEmpty()) {
215 return Akonadi::Item();
217 return *(mRetrievedMsgs.begin());
220 QWidget *KMCommand::parentWidget() const
222 return mParent;
225 bool KMCommand::deletesItself() const
227 return mDeletesItself;
230 void KMCommand::setDeletesItself(bool deletesItself)
232 mDeletesItself = deletesItself;
235 bool KMCommand::emitsCompletedItself() const
237 return mEmitsCompletedItself;
240 void KMCommand::setEmitsCompletedItself(bool emitsCompletedItself)
242 mEmitsCompletedItself = emitsCompletedItself;
245 void KMCommand::setResult(KMCommand::Result result)
247 mResult = result;
250 int KMCommand::mCountJobs = 0;
252 void KMCommand::start()
254 connect(this, &KMCommand::messagesTransfered,
255 this, &KMCommand::slotPostTransfer);
257 if (mMsgList.isEmpty()) {
258 Q_EMIT messagesTransfered(OK);
259 return;
262 // Special case of operating on message that isn't in a folder
263 const Akonadi::Item mb = mMsgList.first();
264 if ((mMsgList.count() == 1) && MessageCore::Util::isStandaloneMessage(mb)) {
265 mRetrievedMsgs.append(mMsgList.takeFirst());
266 Q_EMIT messagesTransfered(OK);
267 return;
270 // we can only retrieve items with a valid id
271 foreach (const Akonadi::Item &item, mMsgList) {
272 if (!item.isValid()) {
273 Q_EMIT messagesTransfered(Failed);
274 return;
278 // transfer the selected messages first
279 transferSelectedMsgs();
282 void KMCommand::slotPostTransfer(KMCommand::Result result)
284 disconnect(this, &KMCommand::messagesTransfered,
285 this, &KMCommand::slotPostTransfer);
286 if (result == OK) {
287 result = execute();
289 mResult = result;
290 if (!emitsCompletedItself()) {
291 Q_EMIT completed(this);
293 if (!deletesItself()) {
294 deleteLater();
298 Akonadi::ItemFetchJob *KMCommand::createFetchJob(const Akonadi::Item::List &items)
300 return new Akonadi::ItemFetchJob(items, this);
303 void KMCommand::transferSelectedMsgs()
305 // make sure no other transfer is active
306 if (KMCommand::mCountJobs > 0) {
307 Q_EMIT messagesTransfered(Failed);
308 return;
311 bool complete = true;
312 KMCommand::mCountJobs = 0;
313 mCountMsgs = 0;
314 mRetrievedMsgs.clear();
315 mCountMsgs = mMsgList.count();
316 uint totalSize = 0;
317 // the QProgressDialog for the user-feedback. Only enable it if it's needed.
318 // For some commands like KMSetStatusCommand it's not needed. Note, that
319 // for some reason the QProgressDialog eats the MouseReleaseEvent (if a
320 // command is executed after the MousePressEvent), cf. bug #71761.
321 if (mCountMsgs > 0) {
322 mProgressDialog = new QProgressDialog(mParent);
323 mProgressDialog.data()->setWindowTitle(i18n("Please wait"));
325 mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count()));
326 mProgressDialog.data()->setModal(true);
327 mProgressDialog.data()->setMinimumDuration(1000);
330 // 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
331 if (!mFetchScope.isEmpty()) {
332 complete = false;
333 ++KMCommand::mCountJobs;
334 Akonadi::ItemFetchJob *fetch = createFetchJob(mMsgList);
335 mFetchScope.fetchAttribute< MailCommon::MDNStateAttribute >();
336 fetch->setFetchScope(mFetchScope);
337 connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, &KMCommand::slotMsgTransfered);
338 connect(fetch, &Akonadi::ItemFetchJob::result, this, &KMCommand::slotJobFinished);
339 } else {
340 // no need to fetch anything
341 if (!mMsgList.isEmpty()) {
342 mRetrievedMsgs = mMsgList;
346 if (complete) {
347 delete mProgressDialog.data();
348 mProgressDialog.clear();
349 Q_EMIT messagesTransfered(OK);
350 } else {
351 // wait for the transfer and tell the progressBar the necessary steps
352 if (mProgressDialog.data()) {
353 connect(mProgressDialog.data(), &QProgressDialog::canceled,
354 this, &KMCommand::slotTransferCancelled);
355 mProgressDialog.data()->setMaximum(totalSize);
360 void KMCommand::slotMsgTransfered(const Akonadi::Item::List &msgs)
362 if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
363 Q_EMIT messagesTransfered(Canceled);
364 return;
366 // save the complete messages
367 #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
368 mRetrievedMsgs.append(msgs);
369 #else
370 Q_FOREACH (const Akonadi::Item &item, msgs) {
371 mRetrievedMsgs.append(item);
373 #endif
375 //mRetrievedMsgs.append(msgs);
378 void KMCommand::slotJobFinished()
380 // the job is finished (with / without error)
381 KMCommand::mCountJobs--;
383 if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
384 return;
387 if (mCountMsgs > mRetrievedMsgs.count()) {
388 // the message wasn't retrieved before => error
389 if (mProgressDialog.data()) {
390 mProgressDialog.data()->hide();
392 slotTransferCancelled();
393 return;
395 // update the progressbar
396 if (mProgressDialog.data()) {
397 mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred",
398 "Please wait while the %1 messages are transferred", KMCommand::mCountJobs));
400 if (KMCommand::mCountJobs == 0) {
401 // all done
402 delete mProgressDialog.data();
403 mProgressDialog.clear();
404 Q_EMIT messagesTransfered(OK);
408 void KMCommand::slotTransferCancelled()
410 KMCommand::mCountJobs = 0;
411 mCountMsgs = 0;
412 mRetrievedMsgs.clear();
413 Q_EMIT messagesTransfered(Canceled);
416 KMMailtoComposeCommand::KMMailtoComposeCommand(const QUrl &url,
417 const Akonadi::Item &msg)
418 : mUrl(url), mMessage(msg)
422 KMCommand::Result KMMailtoComposeCommand::execute()
424 KMime::Message::Ptr msg(new KMime::Message);
425 uint id = 0;
427 if (mMessage.isValid() && mMessage.parentCollection().isValid()) {
428 QSharedPointer<FolderCollection> fd = FolderCollection::forCollection(mMessage.parentCollection(), false);
429 id = fd->identity();
432 MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
433 msg->contentType()->setCharset("utf-8");
434 msg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8");
436 KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
437 win->setFocusToSubject();
438 win->show();
439 return OK;
442 KMMailtoReplyCommand::KMMailtoReplyCommand(QWidget *parent,
443 const QUrl &url, const Akonadi::Item &msg, const QString &selection)
444 : KMCommand(parent, msg), mUrl(url), mSelection(selection)
446 fetchScope().fetchFullPayload(true);
447 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
450 KMCommand::Result KMMailtoReplyCommand::execute()
452 Akonadi::Item item = retrievedMessage();
453 KMime::Message::Ptr msg = MessageCore::Util::message(item);
454 if (!msg) {
455 return Failed;
457 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
458 factory.setIdentityManager(KMKernel::self()->identityManager());
459 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
460 factory.setMailingListAddresses(KMail::Util::mailingListsFromMessage(item));
461 factory.putRepliesInSameFolder(KMail::Util::putRepliesInSameFolder(item));
462 factory.setReplyStrategy(MessageComposer::ReplyNone);
463 factory.setSelection(mSelection);
464 KMime::Message::Ptr rmsg = factory.createReply().msg;
465 rmsg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8"); //TODO Check the UTF-8
466 bool lastEncrypt = false;
467 bool lastSign = false;
468 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
470 KMail::Composer *win = KMail::makeComposer(rmsg, lastSign, lastEncrypt, KMail::Composer::Reply, 0, mSelection);
471 win->setFocusToEditor();
472 win->show();
474 return OK;
477 KMMailtoForwardCommand::KMMailtoForwardCommand(QWidget *parent,
478 const QUrl &url, const Akonadi::Item &msg)
479 : KMCommand(parent, msg), mUrl(url)
481 fetchScope().fetchFullPayload(true);
482 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
485 KMCommand::Result KMMailtoForwardCommand::execute()
487 //TODO : consider factoring createForward into this method.
488 Akonadi::Item item = retrievedMessage();
489 KMime::Message::Ptr msg = MessageCore::Util::message(item);
490 if (!msg) {
491 return Failed;
493 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
494 factory.setIdentityManager(KMKernel::self()->identityManager());
495 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
496 KMime::Message::Ptr fmsg = factory.createForward();
497 fmsg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl).toLower(), "utf-8"); //TODO check the utf-8
498 bool lastEncrypt = false;
499 bool lastSign = false;
500 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
502 KMail::Composer *win = KMail::makeComposer(fmsg, lastSign, lastEncrypt, KMail::Composer::Forward);
503 win->show();
505 return OK;
508 KMAddBookmarksCommand::KMAddBookmarksCommand(const QUrl &url, QWidget *parent)
509 : KMCommand(parent), mUrl(url)
513 KMCommand::Result KMAddBookmarksCommand::execute()
515 const QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + QLatin1String("konqueror/bookmarks.xml");
516 QFileInfo fileInfo(filename);
517 QDir().mkpath(fileInfo.absolutePath());
518 KBookmarkManager *bookManager = KBookmarkManager::managerForFile(filename, QStringLiteral("konqueror"));
519 KBookmarkGroup group = bookManager->root();
520 group.addBookmark(mUrl.path(), QUrl(mUrl), QString());
521 if (bookManager->save()) {
522 bookManager->emitChanged(group);
525 return OK;
528 KMUrlSaveCommand::KMUrlSaveCommand(const QUrl &url, QWidget *parent)
529 : KMCommand(parent), mUrl(url)
533 KMCommand::Result KMUrlSaveCommand::execute()
535 if (mUrl.isEmpty()) {
536 return OK;
538 const QUrl saveUrl = QFileDialog::getSaveFileUrl(parentWidget(), QString(), mUrl.fileName());
539 if (saveUrl.isEmpty()) {
540 return Canceled;
543 bool fileExists = false;
544 if (saveUrl.isLocalFile()) {
545 fileExists = QFile::exists(saveUrl.toLocalFile());
546 } else {
547 auto job = KIO::stat(saveUrl, KIO::StatJob::DestinationSide, 0);
548 KJobWidgets::setWindow(job, parentWidget());
549 fileExists = job->exec();
552 if (fileExists) {
553 if (KMessageBox::warningContinueCancel(Q_NULLPTR,
554 xi18nc("@info", "File <filename>%1</filename> exists.<nl/>Do you want to replace it?",
555 saveUrl.toDisplayString()), i18n("Save to File"), KGuiItem(i18n("&Replace")))
556 != KMessageBox::Continue) {
557 return Canceled;
560 KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite);
561 connect(job, &KIO::Job::result, this, &KMUrlSaveCommand::slotUrlSaveResult);
562 setEmitsCompletedItself(true);
563 return OK;
566 void KMUrlSaveCommand::slotUrlSaveResult(KJob *job)
568 if (job->error()) {
569 showJobError(job);
570 setResult(Failed);
571 Q_EMIT completed(this);
572 } else {
573 setResult(OK);
574 Q_EMIT completed(this);
578 KMEditMessageCommand::KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg)
579 : KMCommand(parent), mMessage(msg)
583 KMCommand::Result KMEditMessageCommand::execute()
585 if (!mMessage) {
586 return Failed;
589 KMail::Composer *win = KMail::makeComposer();
590 bool lastEncrypt = false;
591 bool lastSign = false;
592 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage);
593 win->setMessage(mMessage, lastSign, lastEncrypt, false, true);
594 win->show();
595 win->setModified(true);
596 return OK;
599 KMEditItemCommand::KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource)
600 : KMCommand(parent, msg)
601 , mDeleteFromSource(deleteFromSource)
603 fetchScope().fetchFullPayload(true);
604 fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
605 fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
606 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
609 KMEditItemCommand::~KMEditItemCommand()
613 KMCommand::Result KMEditItemCommand::execute()
615 Akonadi::Item item = retrievedMessage();
616 if (!item.isValid() || !item.parentCollection().isValid()) {
617 return Failed;
619 KMime::Message::Ptr msg = MessageCore::Util::message(item);
620 if (!msg) {
621 return Failed;
624 if (mDeleteFromSource) {
625 setDeletesItself(true);
626 Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(item);
627 connect(job, &KIO::Job::result, this, &KMEditItemCommand::slotDeleteItem);
629 KMail::Composer *win = KMail::makeComposer();
630 bool lastEncrypt = false;
631 bool lastSign = false;
632 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
633 win->setMessage(msg, lastSign, lastEncrypt, false, true);
635 win->setFolder(item.parentCollection());
637 const MailTransport::TransportAttribute *transportAttribute = item.attribute<MailTransport::TransportAttribute>();
638 if (transportAttribute) {
639 win->setCurrentTransport(transportAttribute->transportId());
640 } else {
641 int transportId = msg->headerByType("X-KMail-Transport") ? msg->headerByType("X-KMail-Transport")->asUnicodeString().toInt() : -1;
642 if (transportId != -1) {
643 win->setCurrentTransport(transportId);
647 if (auto hdr = msg->replyTo(false)) {
648 const QString replyTo = hdr->asUnicodeString();
649 win->setCurrentReplyTo(replyTo);
652 const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute<MailTransport::SentBehaviourAttribute>();
653 if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) {
654 win->setFcc(QString::number(sentAttribute->moveToCollection().id()));
656 win->show();
657 if (mDeleteFromSource) {
658 win->setModified(true);
661 return OK;
664 void KMEditItemCommand::slotDeleteItem(KJob *job)
666 if (job->error()) {
667 showJobError(job);
668 setResult(Failed);
669 Q_EMIT completed(this);
670 } else {
671 setResult(OK);
672 Q_EMIT completed(this);
674 deleteLater();
677 KMUseTemplateCommand::KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg)
678 : KMCommand(parent, msg)
680 fetchScope().fetchFullPayload(true);
681 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
684 KMCommand::Result KMUseTemplateCommand::execute()
686 Akonadi::Item item = retrievedMessage();
687 if (!item.isValid()
688 || !item.parentCollection().isValid() ||
689 !CommonKernel->folderIsTemplates(item.parentCollection())
691 return Failed;
693 KMime::Message::Ptr msg = MessageCore::Util::message(item);
694 if (!msg) {
695 return Failed;
698 KMime::Message::Ptr newMsg(new KMime::Message);
699 newMsg->setContent(msg->encodedContent());
700 newMsg->parse();
701 // these fields need to be regenerated for the new message
702 newMsg->removeHeader<KMime::Headers::Date>();
703 newMsg->removeHeader<KMime::Headers::MessageID>();
705 KMail::Composer *win = KMail::makeComposer();
707 win->setMessage(newMsg, false, false, false, true);
708 win->show();
709 return OK;
712 KMSaveMsgCommand::KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList)
713 : KMCommand(parent, msgList)
715 if (msgList.empty()) {
716 return;
719 fetchScope().fetchFullPayload(true); // ### unless we call the corresponding KMCommand ctor, this has no effect
722 KMCommand::Result KMSaveMsgCommand::execute()
724 if (!MessageViewer::Util::saveMessageInMbox(retrievedMsgs(), parentWidget())) {
725 return Failed;
727 return OK;
730 //-----------------------------------------------------------------------------
732 KMOpenMsgCommand::KMOpenMsgCommand(QWidget *parent, const QUrl &url,
733 const QString &encoding, KMMainWidget *main)
734 : KMCommand(parent),
735 mUrl(url),
736 mJob(Q_NULLPTR),
737 mEncoding(encoding),
738 mMainWidget(main)
740 qCDebug(KMAIL_LOG) << "url :" << url;
743 KMCommand::Result KMOpenMsgCommand::execute()
745 if (mUrl.isEmpty()) {
746 mUrl = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Open Message"), QUrl(),
747 i18n("Message (*.mbox)")
750 if (mUrl.isEmpty()) {
751 return Canceled;
754 if (mMainWidget) {
755 mMainWidget->addRecentFile(mUrl);
758 setDeletesItself(true);
759 mJob = KIO::get(mUrl, KIO::NoReload, KIO::HideProgressInfo);
760 connect(mJob, &KIO::TransferJob::data,
761 this, &KMOpenMsgCommand::slotDataArrived);
762 connect(mJob, &KJob::result,
763 this, &KMOpenMsgCommand::slotResult);
764 setEmitsCompletedItself(true);
765 return OK;
768 void KMOpenMsgCommand::slotDataArrived(KIO::Job *, const QByteArray &data)
770 if (data.isEmpty()) {
771 return;
774 mMsgString.append(QString::fromLatin1(data.data()));
777 void KMOpenMsgCommand::doesNotContainMessage()
779 KMessageBox::sorry(parentWidget(),
780 i18n("The file does not contain a message."));
781 setResult(Failed);
782 Q_EMIT completed(this);
783 // Emulate closing of a secondary window so that KMail exits in case it
784 // was started with the --view command line option. Otherwise an
785 // invisible KMail would keep running.
786 SecondaryWindow *win = new SecondaryWindow();
787 win->close();
788 win->deleteLater();
789 deleteLater();
793 void KMOpenMsgCommand::slotResult(KJob *job)
795 if (job->error()) {
796 // handle errors
797 showJobError(job);
798 setResult(Failed);
799 Q_EMIT completed(this);
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);
839 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 deleteLater();
1467 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder,
1468 const Akonadi::Item::List &msgList,
1469 MessageList::Core::MessageItemSetReference ref)
1470 : KMCommand(Q_NULLPTR, msgList), mDestFolder(destFolder), mProgressItem(Q_NULLPTR), mRef(ref)
1472 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1475 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder,
1476 const Akonadi::Item &msg,
1477 MessageList::Core::MessageItemSetReference ref)
1478 : KMCommand(Q_NULLPTR, msg), mDestFolder(destFolder), mProgressItem(Q_NULLPTR), mRef(ref)
1480 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1483 void KMMoveCommand::slotMoveResult(KJob *job)
1485 if (job->error()) {
1486 // handle errors
1487 showJobError(job);
1488 completeMove(Failed);
1489 } else {
1490 completeMove(OK);
1494 KMCommand::Result KMMoveCommand::execute()
1496 #ifndef QT_NO_CURSOR
1497 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
1498 #endif
1499 setEmitsCompletedItself(true);
1500 setDeletesItself(true);
1501 Akonadi::Item::List retrievedList = retrievedMsgs();
1502 if (!retrievedList.isEmpty()) {
1503 if (mDestFolder.isValid()) {
1504 Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(retrievedList, mDestFolder, this);
1505 connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1507 // group by source folder for undo
1508 std::sort(retrievedList.begin(), retrievedList.end(), boost::bind(&Akonadi::Item::storageCollectionId, _1) <
1509 boost::bind(&Akonadi::Item::storageCollectionId, _2));
1510 Akonadi::Collection parent;
1511 int undoId = -1;
1512 foreach (const Akonadi::Item &item, retrievedList) {
1513 if (item.storageCollectionId() <= 0) {
1514 continue;
1516 if (parent.id() != item.storageCollectionId()) {
1517 parent = Akonadi::Collection(item.storageCollectionId());
1518 undoId = kmkernel->undoStack()->newUndoAction(parent, mDestFolder);
1520 kmkernel->undoStack()->addMsgToAction(undoId, item);
1522 } else {
1523 Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(retrievedList, this);
1524 connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1526 } else {
1527 deleteLater();
1528 return Failed;
1530 // TODO set SSL state according to source and destfolder connection?
1531 Q_ASSERT(!mProgressItem);
1532 mProgressItem =
1533 ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(),
1534 mDestFolder.isValid() ? i18n("Moving messages") : i18n("Deleting messages"), QString(), true, KPIM::ProgressItem::Unknown);
1535 mProgressItem->setUsesBusyIndicator(true);
1536 connect(mProgressItem, &ProgressItem::progressItemCanceled,
1537 this, &KMMoveCommand::slotMoveCanceled);
1538 return OK;
1541 void KMMoveCommand::completeMove(Result result)
1543 if (mProgressItem) {
1544 mProgressItem->setComplete();
1545 mProgressItem = Q_NULLPTR;
1547 setResult(result);
1548 Q_EMIT moveDone(this);
1549 Q_EMIT completed(this);
1550 deleteLater();
1553 void KMMoveCommand::slotMoveCanceled()
1555 completeMove(Canceled);
1558 // srcFolder doesn't make much sense for searchFolders
1559 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder,
1560 const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref)
1561 : KMMoveCommand(findTrashFolder(srcFolder), msgList, ref)
1565 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref)
1566 : KMMoveCommand(findTrashFolder(srcFolder), msg, ref)
1570 Akonadi::Collection KMTrashMsgCommand::findTrashFolder(const Akonadi::Collection &folder)
1572 Akonadi::Collection col = CommonKernel->trashCollectionFromResource(folder);
1573 if (!col.isValid()) {
1574 col = CommonKernel->trashCollectionFolder();
1576 if (folder != col) {
1577 return col;
1579 return Akonadi::Collection();
1582 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer)
1583 : KMCommand(parent, msg),
1584 mViewer(viewer)
1586 fetchScope().fetchFullPayload(true);
1589 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs)
1590 : KMCommand(parent, msgs),
1591 mViewer(Q_NULLPTR)
1593 fetchScope().fetchFullPayload(true);
1596 KMCommand::Result KMSaveAttachmentsCommand::execute()
1598 KMime::Content::List contentsToSave;
1599 foreach (const Akonadi::Item &item, retrievedMsgs()) {
1600 if (item.hasPayload<KMime::Message::Ptr>()) {
1601 contentsToSave += item.payload<KMime::Message::Ptr>()->attachments();
1602 } else {
1603 qCWarning(KMAIL_LOG) << "Retrieved item has no payload? Ignoring for saving the attachments";
1606 QUrl currentUrl;
1607 if (MessageViewer::Util::saveAttachments(contentsToSave, parentWidget(), currentUrl)) {
1608 if (mViewer) {
1609 mViewer->showOpenAttachmentFolderWidget(currentUrl);
1611 return OK;
1613 return Failed;
1616 KMResendMessageCommand::KMResendMessageCommand(QWidget *parent,
1617 const Akonadi::Item &msg)
1618 : KMCommand(parent, msg)
1620 fetchScope().fetchFullPayload(true);
1621 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1624 KMCommand::Result KMResendMessageCommand::execute()
1626 Akonadi::Item item = retrievedMessage();
1627 KMime::Message::Ptr msg = MessageCore::Util::message(item);
1628 if (!msg) {
1629 return Failed;
1632 MessageFactory factory(msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()));
1633 factory.setIdentityManager(KMKernel::self()->identityManager());
1634 factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1635 KMime::Message::Ptr newMsg = factory.createResend();
1636 newMsg->contentType()->setCharset(MessageViewer::NodeHelper::charset(msg.data()));
1638 KMail::Composer *win = KMail::makeComposer();
1639 if (auto hdr = msg->replyTo(false)) {
1640 const QString replyTo = hdr->asUnicodeString();
1641 win->setCurrentReplyTo(replyTo);
1643 bool lastEncrypt = false;
1644 bool lastSign = false;
1645 KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
1646 win->setMessage(newMsg, lastSign, lastEncrypt, false, true);
1648 win->show();
1650 return OK;
1653 KMShareImageCommand::KMShareImageCommand(const QUrl &url, QWidget *parent)
1654 : KMCommand(parent),
1655 mUrl(url)
1659 KMCommand::Result KMShareImageCommand::execute()
1661 KMime::Message::Ptr msg(new KMime::Message);
1662 uint id = 0;
1664 MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
1665 msg->contentType()->setCharset("utf-8");
1667 KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
1668 win->setFocusToSubject();
1669 win->addAttachment(mUrl, i18n("Image"));
1670 win->show();
1671 return OK;
1674 KMFetchMessageCommand::KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item)
1675 : KMCommand(parent, item)
1677 // Workaround KMCommand::transferSelectedMsgs() expecting non-empty fetchscope
1678 fetchScope().fetchFullPayload(true);
1681 Akonadi::ItemFetchJob *KMFetchMessageCommand::createFetchJob(const Akonadi::Item::List &items)
1683 Q_ASSERT(items.size() == 1);
1684 Akonadi::ItemFetchJob *fetch = MessageViewer::Viewer::createFetchJob(items.first());
1685 fetchScope() = fetch->fetchScope();
1686 return fetch;
1689 KMCommand::Result KMFetchMessageCommand::execute()
1691 Akonadi::Item item = retrievedMessage();
1692 if (!item.isValid() || !item.hasPayload<KMime::Message::Ptr>()) {
1693 return Failed;
1696 mItem = item;
1697 return OK;
1700 Akonadi::Item KMFetchMessageCommand::item() const
1702 return mItem;