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"
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>
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>
108 #include <Libkdepim/KCursorSaver>
111 #include <Libkleo/CryptoBackend>
112 #include <Libkleo/CryptoBackendFactory>
114 #include <gpgme++/error.h>
116 #include <boost/bind.hpp>
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>
132 #include <KIO/JobUiDelegate>
133 #include <KIO/StatJob>
135 #include <QApplication>
136 #include <QByteArray>
137 #include <QFileDialog>
138 #include <QFontDatabase>
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
;
159 return KMail::Composer::Reply
;
163 /// Helper to sanely show an error message for a job
164 static void showJobError(KJob
*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();
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
)
199 KMCommand::~KMCommand()
203 KMCommand::Result
KMCommand::result() const
205 if (mResult
== Undefined
) {
206 qCDebug(KMAIL_LOG
) << "mResult is Undefined";
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
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
)
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
);
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
);
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
);
282 // transfer the selected messages first
283 transferSelectedMsgs();
286 void KMCommand::slotPostTransfer(KMCommand::Result result
)
288 disconnect(this, &KMCommand::messagesTransfered
,
289 this, &KMCommand::slotPostTransfer
);
294 if (!emitsCompletedItself()) {
295 Q_EMIT
completed(this);
297 if (!deletesItself()) {
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
);
315 bool complete
= true;
316 KMCommand::mCountJobs
= 0;
318 mRetrievedMsgs
.clear();
319 mCountMsgs
= mMsgList
.count();
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()) {
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
);
344 // no need to fetch anything
345 if (!mMsgList
.isEmpty()) {
346 mRetrievedMsgs
= mMsgList
;
351 delete mProgressDialog
.data();
352 mProgressDialog
.clear();
353 Q_EMIT
messagesTransfered(OK
);
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
);
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()) {
383 if (mCountMsgs
> mRetrievedMsgs
.count()) {
384 // the message wasn't retrieved before => error
385 if (mProgressDialog
.data()) {
386 mProgressDialog
.data()->hide();
388 slotTransferCancelled();
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) {
398 delete mProgressDialog
.data();
399 mProgressDialog
.clear();
400 Q_EMIT
messagesTransfered(OK
);
404 void KMCommand::slotTransferCancelled()
406 KMCommand::mCountJobs
= 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
);
423 if (mMessage
.isValid() && mMessage
.parentCollection().isValid()) {
424 QSharedPointer
<FolderCollection
> fd
= FolderCollection::forCollection(mMessage
.parentCollection(), false);
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();
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
);
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();
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
);
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
);
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
);
524 KMUrlSaveCommand::KMUrlSaveCommand(const QUrl
&url
, QWidget
*parent
)
525 : KMCommand(parent
), mUrl(url
)
529 KMCommand::Result
KMUrlSaveCommand::execute()
531 if (mUrl
.isEmpty()) {
534 QString recentDirClass
;
535 const QUrl saveUrl
= QFileDialog::getSaveFileUrl(parentWidget()
536 , KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///OpenMessage")), recentDirClass
).toLocalFile()
538 if (saveUrl
.isEmpty()) {
542 if (!recentDirClass
.isEmpty()) {
543 KRecentDirs::add(recentDirClass
, saveUrl
.path());
546 bool fileExists
= false;
547 if (saveUrl
.isLocalFile()) {
548 fileExists
= QFile::exists(saveUrl
.toLocalFile());
550 auto job
= KIO::stat(saveUrl
, KIO::StatJob::DestinationSide
, 0);
551 KJobWidgets::setWindow(job
, parentWidget());
552 fileExists
= job
->exec();
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
) {
563 KIO::Job
*job
= KIO::file_copy(mUrl
, saveUrl
, -1, KIO::Overwrite
);
564 connect(job
, &KIO::Job::result
, this, &KMUrlSaveCommand::slotUrlSaveResult
);
565 setEmitsCompletedItself(true);
569 void KMUrlSaveCommand::slotUrlSaveResult(KJob
*job
)
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()
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);
597 win
->setModified(true);
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()) {
621 KMime::Message::Ptr msg
= MessageCore::Util::message(item
);
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());
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()));
659 if (mDeleteFromSource
) {
660 win
->setModified(true);
666 void KMEditItemCommand::slotDeleteItem(KJob
*job
)
674 Q_EMIT
completed(this);
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();
689 || !item
.parentCollection().isValid() ||
690 !CommonKernel
->folderIsTemplates(item
.parentCollection())
694 KMime::Message::Ptr msg
= MessageCore::Util::message(item
);
699 KMime::Message::Ptr
newMsg(new KMime::Message
);
700 newMsg
->setContent(msg
->encodedContent());
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);
713 KMSaveMsgCommand::KMSaveMsgCommand(QWidget
*parent
, const Akonadi::Item::List
&msgList
)
714 : KMCommand(parent
, msgList
)
716 if (msgList
.empty()) {
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())) {
731 //-----------------------------------------------------------------------------
733 KMOpenMsgCommand::KMOpenMsgCommand(QWidget
*parent
, const QUrl
&url
,
734 const QString
&encoding
, KMMainWidget
*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()) {
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);
769 void KMOpenMsgCommand::slotDataArrived(KIO::Job
*, const QByteArray
&data
)
771 if (data
.isEmpty()) {
775 mMsgString
.append(QString::fromLatin1(data
.data()));
778 void KMOpenMsgCommand::doesNotContainMessage()
780 KMessageBox::sorry(parentWidget(),
781 i18n("The file does not contain a message."));
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();
794 void KMOpenMsgCommand::slotResult(KJob
*job
)
801 if (mMsgString
.isEmpty()) {
802 qCDebug(KMAIL_LOG
) << " Message not found. There is a problem";
803 doesNotContainMessage();
806 int startOfMessage
= 0;
807 if (mMsgString
.startsWith(QStringLiteral("From "))) {
808 startOfMessage
= mMsgString
.indexOf(QLatin1Char('\n'));
809 if (startOfMessage
== -1) {
810 doesNotContainMessage();
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()));
825 if (!msg
->hasContent()) {
826 delete msg
; msg
= Q_NULLPTR
;
827 doesNotContainMessage();
830 KMReaderMainWin
*win
= new KMReaderMainWin();
831 KMime::Message::Ptr
mMsg(msg
);
832 win
->showMessage(mEncoding
, mMsg
);
834 if (multipleMessages
)
835 KMessageBox::information(win
,
836 i18n("The file contains multiple messages. "
837 "Only the first message is shown."));
840 Q_EMIT
completed(this);
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
),
855 fetchScope().fetchFullPayload(true);
858 fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent
);
861 KMCommand::Result
KMReplyCommand::execute()
864 KPIM::KCursorSaver
busy(KPIM::KBusyPtr::busy());
866 Akonadi::Item item
= retrievedMessage();
867 KMime::Message::Ptr msg
= MessageCore::Util::message(item
);
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
);
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();
898 KMForwardCommand::KMForwardCommand(QWidget
*parent
,
899 const Akonadi::Item::List
&msgList
, uint identity
, const QString
&templateName
)
900 : KMCommand(parent
, msgList
),
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
),
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
);
925 KPIM::KCursorSaver
busy(KPIM::KBusyPtr::busy());
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
);
945 KMail::Composer
*win
= KMail::makeComposer(fwdMsg
, lastSign
, lastEncrypt
, KMail::Composer::Forward
, id
, QString(), mTemplate
);
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(
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
);
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
) {
993 // forward a single message at most.
994 Akonadi::Item item
= msgList
.first();
995 if (createComposer(item
) == Failed
) {
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
);
1029 mWin
= KMail::makeComposer(fwdMsg
.first
, false, false, KMail::Composer::Forward
, mIdentity
);
1031 foreach (KMime::Content
*attach
, fwdMsg
.second
) {
1032 mWin
->addAttach(attach
);
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
) {
1071 if (!TransportManager::self()->showTransportCreationDialog(parentWidget(), TransportManager::IfNoTransportExists
)) {
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
);
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
);
1106 const MailTransport::SentBehaviourAttribute
*sentAttribute
= item
.attribute
<MailTransport::SentBehaviourAttribute
>();
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
);
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
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
),
1141 mHtmlLoadExtOverride(htmlLoadExtOverride
),
1142 mUseFixedFont(useFixedFont
),
1143 mPrintPreview(false)
1145 fetchScope().fetchFullPayload(true);
1146 if (MessageCore::MessageCoreSettings::useDefaultFonts()) {
1147 mOverrideFont
= QFontDatabase::systemFont(QFontDatabase::GeneralFont
);
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());
1189 printerWin
->viewer()->printMessage(retrievedMessage());
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.
1207 const Akonadi::Item first
= retrievedMsgs().first();
1208 MessageStatus pStatus
;
1209 pStatus
.setStatusFromFlags(first
.flags());
1210 if (pStatus
& mStatus
) {
1211 parentStatus
= true;
1213 parentStatus
= false;
1217 Akonadi::Item::List itemsToModify
;
1218 foreach (const Akonadi::Item
&it
, retrievedMsgs()) {
1220 //qCDebug(KMAIL_LOG)<<" item ::"<<tmpItem;
1223 MessageStatus itemStatus
;
1224 itemStatus
.setStatusFromFlags(it
.flags());
1225 if (itemStatus
& mStatus
) {
1230 if (myStatus
!= parentStatus
) {
1235 Akonadi::Item
item(it
);
1236 const Akonadi::Item::Flag flag
= *(mStatus
.statusFlags().begin());
1238 if (item
.hasFlag(flag
)) {
1239 item
.clearFlag(flag
);
1240 itemsToModify
.push_back(item
);
1243 itemsToModify
.push_back(item
);
1246 if (!item
.hasFlag(flag
)) {
1248 itemsToModify
.push_back(item
);
1253 if (itemsToModify
.isEmpty()) {
1254 slotModifyItemDone(Q_NULLPTR
); // pretend we did something
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
);
1264 void KMSetStatusCommand::slotModifyItemDone(KJob
*job
)
1266 if (job
&& job
->error()) {
1267 qCWarning(KMAIL_LOG
) << " Error trying to set item status:" << job
->errorText();
1272 KMSetTagCommand::KMSetTagCommand(const Akonadi::Tag::List
&tags
, const Akonadi::Item::List
&item
,
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
);
1288 mCreatedTags
<< tag
;
1292 if (mCreatedTags
.size() == mTags
.size()) {
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()) {
1315 if (mMode
== KMSetTagCommand::Toggle
) {
1316 Q_FOREACH (const Akonadi::Tag
&tag
, mCreatedTags
) {
1317 if (item
.hasTag(tag
)) {
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
)) {
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();
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());
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
;
1397 KMMetaFilterActionCommand::KMMetaFilterActionCommand(const QString
&filterId
,
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()
1421 Akonadi::Item item
= retrievedMessage();
1422 KMime::Message::Ptr msg
= MessageCore::Util::message(item
);
1426 if (!MailingList::name(msg
, name
, value
).isEmpty()) {
1427 FilterIf
->openFilterDialog(false);
1428 FilterIf
->createFilter(name
, value
);
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
);
1457 void KMCopyCommand::slotCopyResult(KJob
*job
)
1464 Q_EMIT
completed(this);
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
)
1489 completeMove(Failed
);
1495 KMCommand::Result
KMMoveCommand::execute()
1497 #ifndef QT_NO_CURSOR
1498 KPIM::KCursorSaver
busy(KPIM::KBusyPtr::busy());
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
;
1513 foreach (const Akonadi::Item
&item
, retrievedList
) {
1514 if (item
.storageCollectionId() <= 0) {
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
);
1524 Akonadi::ItemDeleteJob
*job
= new Akonadi::ItemDeleteJob(retrievedList
, this);
1525 connect(job
, &KIO::Job::result
, this, &KMMoveCommand::slotMoveResult
);
1531 // TODO set SSL state according to source and destfolder connection?
1532 Q_ASSERT(!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
);
1542 void KMMoveCommand::completeMove(Result result
)
1544 if (mProgressItem
) {
1545 mProgressItem
->setComplete();
1546 mProgressItem
= Q_NULLPTR
;
1549 Q_EMIT
moveDone(this);
1550 Q_EMIT
completed(this);
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
) {
1580 return Akonadi::Collection();
1583 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget
*parent
, const Akonadi::Item
&msg
, MessageViewer::Viewer
*viewer
)
1584 : KMCommand(parent
, msg
),
1587 fetchScope().fetchFullPayload(true);
1590 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget
*parent
, const Akonadi::Item::List
&msgs
)
1591 : KMCommand(parent
, msgs
),
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();
1604 qCWarning(KMAIL_LOG
) << "Retrieved item has no payload? Ignoring for saving the attachments";
1608 if (MessageViewer::Util::saveAttachments(contentsToSave
, parentWidget(), currentUrl
)) {
1610 mViewer
->showOpenAttachmentFolderWidget(currentUrl
);
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
);
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);
1654 KMShareImageCommand::KMShareImageCommand(const QUrl
&url
, QWidget
*parent
)
1655 : KMCommand(parent
),
1660 KMCommand::Result
KMShareImageCommand::execute()
1662 KMime::Message::Ptr
msg(new KMime::Message
);
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"));
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();
1690 KMCommand::Result
KMFetchMessageCommand::execute()
1692 Akonadi::Item item
= retrievedMessage();
1693 if (!item
.isValid() || !item
.hasPayload
<KMime::Message::Ptr
>()) {
1701 Akonadi::Item
KMFetchMessageCommand::item() const