File indexing completed on 2024-10-27 04:51:02

0001 /*
0002  * This file is part of KMail.
0003  * SPDX-FileCopyrightText: 2011-2024 Laurent Montel <montel@kde.org>
0004  *
0005  * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0006  *
0007  * Based on KMail code by:
0008  * SPDX-FileCopyrightText: 1997 Markus Wuebben <markus.wuebben@kde.org>
0009  *
0010  * SPDX-License-Identifier: GPL-2.0-or-later
0011  */
0012 #include "kmcomposerwin.h"
0013 #include "subjectlineeditwithautocorrection.h"
0014 // KMail includes
0015 #include "attachment/attachmentcontroller.h"
0016 #include "attachment/attachmentview.h"
0017 #include "custommimeheader.h"
0018 #include "editor/kmcomposereditorng.h"
0019 #include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h"
0020 #include "editor/plugininterface/kmailplugineditorconverttextmanagerinterface.h"
0021 #include "editor/plugininterface/kmailplugineditorinitmanagerinterface.h"
0022 #include "editor/plugininterface/kmailplugineditormanagerinterface.h"
0023 #include "editor/plugininterface/kmailplugingrammareditormanagerinterface.h"
0024 #include "editor/potentialphishingemail/potentialphishingemailjob.h"
0025 #include "editor/potentialphishingemail/potentialphishingemailwarning.h"
0026 #include "editor/warningwidgets/attachmentaddedfromexternalwarning.h"
0027 #include "editor/warningwidgets/incorrectidentityfolderwarning.h"
0028 #include "editor/warningwidgets/nearexpirywarning.h"
0029 #include "editor/warningwidgets/toomanyrecipientswarning.h"
0030 #include "job/addressvalidationjob.h"
0031 #include "job/dndfromarkjob.h"
0032 #include "job/saveasfilejob.h"
0033 #include "job/savedraftjob.h"
0034 #include "kmail_debug.h"
0035 #include "kmcommands.h"
0036 #include "kmcomposercreatenewcomposerjob.h"
0037 #include "kmcomposerglobalaction.h"
0038 #include "kmcomposerupdatetemplatejob.h"
0039 #include "kmkernel.h"
0040 #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff...
0041 #include "settings/kmailsettings.h"
0042 #include "undosend/undosendmanager.h"
0043 #include "util.h"
0044 #include "validatesendmailshortcut.h"
0045 #include "warningwidgets/attachmentmissingwarning.h"
0046 #include "warningwidgets/externaleditorwarning.h"
0047 #include "widgets/cryptostateindicatorwidget.h"
0048 #include "widgets/kactionmenutransport.h"
0049 #include <templateparser/templatesconfiguration_kfg.h>
0050 
0051 #include <Akonadi/ChangeRecorder>
0052 #include <Akonadi/ContactGroupExpandJob>
0053 #include <Akonadi/ItemFetchJob>
0054 #include <Akonadi/MessageFlags>
0055 #include <Akonadi/MessageStatus>
0056 #include <Akonadi/Monitor>
0057 
0058 #include <KContacts/VCardConverter>
0059 
0060 #include <KIdentityManagementCore/Identity>
0061 #include <KIdentityManagementCore/IdentityManager>
0062 #include <KIdentityManagementCore/Signature>
0063 #include <KIdentityManagementWidgets/IdentityCombo>
0064 
0065 #include <KMime/Message>
0066 
0067 #include <KPIMTextEdit/RichTextComposerActions>
0068 #include <KPIMTextEdit/RichTextComposerControler>
0069 #include <KPIMTextEdit/RichTextComposerImages>
0070 #include <KPIMTextEdit/RichTextExternalComposer>
0071 #include <TextCustomEditor/RichTextEditorWidget>
0072 
0073 #include <Libkdepim/ProgressStatusBarWidget>
0074 #include <Libkdepim/StatusbarProgressWidget>
0075 
0076 #include <KCursorSaver>
0077 
0078 #include <Libkleo/ExpiryChecker>
0079 #include <Libkleo/KeyCache>
0080 #include <Libkleo/KeySelectionDialog>
0081 
0082 #include <MailCommon/FolderCollectionMonitor>
0083 #include <MailCommon/FolderRequester>
0084 #include <MailCommon/FolderSettings>
0085 #include <MailCommon/MailKernel>
0086 #include <MailCommon/SnippetTreeView>
0087 #include <MailCommon/SnippetsManager>
0088 
0089 #include <MailTransport/Transport>
0090 #include <MailTransport/TransportComboBox>
0091 #include <MailTransport/TransportManager>
0092 
0093 #include <MessageComposer/AttachmentModel>
0094 #include <MessageComposer/Composer>
0095 #include <MessageComposer/ComposerLineEdit>
0096 #include <MessageComposer/ComposerViewInterface>
0097 #include <MessageComposer/ConvertSnippetVariablesJob>
0098 #include <MessageComposer/DraftStatus>
0099 #include <MessageComposer/FollowUpReminderSelectDateDialog>
0100 #include <MessageComposer/FollowupReminder>
0101 #include <MessageComposer/FollowupReminderCreateJob>
0102 #include <MessageComposer/GlobalPart>
0103 #include <MessageComposer/InfoPart>
0104 #include <MessageComposer/InsertTextFileJob>
0105 #include <MessageComposer/Kleo_Util>
0106 #include <MessageComposer/MessageComposerSettings>
0107 #include <MessageComposer/MessageHelper>
0108 #include <MessageComposer/PluginActionType>
0109 #include <MessageComposer/PluginEditorCheckBeforeSendParams>
0110 #include <MessageComposer/PluginEditorConverterBeforeConvertingData>
0111 #include <MessageComposer/PluginEditorConverterInitialData>
0112 #include <MessageComposer/PluginEditorInterface>
0113 #include <MessageComposer/RecipientsEditor>
0114 #include <MessageComposer/RichTextComposerSignatures>
0115 #include <MessageComposer/SendLaterDialog>
0116 #include <MessageComposer/SendLaterInfo>
0117 #include <MessageComposer/SendLaterUtil>
0118 #include <MessageComposer/SignatureController>
0119 #include <MessageComposer/StatusBarLabelToggledState>
0120 #include <MessageComposer/TextPart>
0121 #include <MessageComposer/Util>
0122 
0123 #include <Sonnet/DictionaryComboBox>
0124 
0125 #include <MessageCore/AttachmentPart>
0126 #include <MessageCore/AutocryptStorage>
0127 #include <MessageCore/MessageCoreSettings>
0128 #include <MessageCore/NodeHelper>
0129 #include <MessageCore/StringUtil>
0130 
0131 #include <MessageViewer/MessageViewerSettings>
0132 #include <MessageViewer/Stl_Util>
0133 
0134 #include <MimeTreeParser/NodeHelper>
0135 #include <MimeTreeParser/ObjectTreeParser>
0136 #include <MimeTreeParser/SimpleObjectTreeSource>
0137 
0138 #include <PimCommon/CustomToolsPluginManager>
0139 #include <PimCommon/CustomToolsWidgetng>
0140 #include <PimCommon/KActionMenuChangeCase>
0141 #include <PimCommon/LineEditWithAutoCorrection>
0142 #include <PimCommon/PurposeMenuMessageWidget>
0143 
0144 #include <TemplateParser/TemplateParserJob>
0145 #include <TemplateParser/TemplatesConfiguration>
0146 
0147 #include <QGpgME/Protocol>
0148 
0149 // KDE Frameworks includes
0150 #include <KActionCollection>
0151 #include <KActionMenu>
0152 #include <KConfigGroup>
0153 #include <KEditToolBar>
0154 #include <KEmailAddress>
0155 #include <KEncodingFileDialog>
0156 #include <KFileWidget>
0157 #include <KIO/JobUiDelegate>
0158 #include <KIconUtils>
0159 #include <KMessageBox>
0160 #include <KRecentDirs>
0161 #include <KRecentFilesAction>
0162 #include <KShortcutsDialog>
0163 #include <KSplitterCollapserButton>
0164 #include <KStandardShortcut>
0165 #include <KToggleAction>
0166 #include <KToolBar>
0167 #include <KXMLGUIFactory>
0168 
0169 #include <QDBusConnection>
0170 // Qt includes
0171 #include <QAction>
0172 #include <QApplication>
0173 #include <QCheckBox>
0174 #include <QClipboard>
0175 #include <QFontDatabase>
0176 #include <QInputDialog>
0177 #include <QMenu>
0178 #include <QMenuBar>
0179 #include <QMimeData>
0180 #include <QPointer>
0181 #include <QShortcut>
0182 #include <QSplitter>
0183 #include <QStandardPaths>
0184 #include <QStatusBar>
0185 #include <QUrlQuery>
0186 
0187 // GPGME
0188 #include <gpgme++/key.h>
0189 #include <gpgme++/keylistresult.h>
0190 #include <gpgme++/tofuinfo.h>
0191 
0192 #include <KDialogJobUiDelegate>
0193 #include <KIO/CommandLauncherJob>
0194 #include <chrono>
0195 
0196 using namespace std::chrono_literals;
0197 
0198 using MailTransport::Transport;
0199 using MailTransport::TransportManager;
0200 using Sonnet::DictionaryComboBox;
0201 
0202 Q_DECLARE_METATYPE(MessageComposer::Recipient::Ptr)
0203 
0204 KMail::Composer *KMail::makeComposer(const KMime::Message::Ptr &msg,
0205                                      bool lastSignState,
0206                                      bool lastEncryptState,
0207                                      Composer::TemplateContext context,
0208                                      uint identity,
0209                                      const QString &textSelection,
0210                                      const QString &customTemplate)
0211 {
0212     return KMComposerWin::create(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate);
0213 }
0214 
0215 KMail::Composer *KMComposerWin::create(const KMime::Message::Ptr &msg,
0216                                        bool lastSignState,
0217                                        bool lastEncryptState,
0218                                        Composer::TemplateContext context,
0219                                        uint identity,
0220                                        const QString &textSelection,
0221                                        const QString &customTemplate)
0222 {
0223     return new KMComposerWin(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate);
0224 }
0225 
0226 int KMComposerWin::s_composerNumber = 0;
0227 
0228 KMComposerWin::KMComposerWin(const KMime::Message::Ptr &aMsg,
0229                              bool lastSignState,
0230                              bool lastEncryptState,
0231                              Composer::TemplateContext context,
0232                              uint id,
0233                              const QString &textSelection,
0234                              const QString &customTemplate)
0235     : KMail::Composer(QStringLiteral("kmail-composer#"))
0236     , mTextSelection(textSelection)
0237     , mCustomTemplate(customTemplate)
0238     , mFolder(Akonadi::Collection(-1))
0239     , mId(id)
0240     , mContext(context)
0241     , mAttachmentMissing(new AttachmentMissingWarning(this))
0242     , mExternalEditorWarning(new ExternalEditorWarning(this))
0243     , mTooMyRecipientWarning(new TooManyRecipientsWarning(this))
0244     , mNearExpiryWarning(new NearExpiryWarning(this))
0245     , mCryptoStateIndicatorWidget(new CryptoStateIndicatorWidget(this))
0246     , mPotentialPhishingEmailWarning(new PotentialPhishingEmailWarning(this))
0247     , mIncorrectIdentityFolderWarning(new IncorrectIdentityFolderWarning(this))
0248     , mPluginEditorManagerInterface(new KMailPluginEditorManagerInterface(this))
0249     , mPluginEditorGrammarManagerInterface(new KMailPluginGrammarEditorManagerInterface(this))
0250     , mAttachmentFromExternalMissing(new AttachmentAddedFromExternalWarning(this))
0251     , mPluginEditorMessageWidget(new PimCommon::PurposeMenuMessageWidget(this))
0252     , mKeyCache(Kleo::KeyCache::mutableInstance())
0253 {
0254     mGlobalAction = new KMComposerGlobalAction(this, this);
0255     mComposerBase = new MessageComposer::ComposerViewBase(this, this);
0256     mComposerBase->setIdentityManager(kmkernel->identityManager());
0257 
0258     connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::message, this, &KMComposerWin::slotMessage);
0259     connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::insertText, this, &KMComposerWin::slotEditorPluginInsertText);
0260     mPluginEditorCheckBeforeSendManagerInterface = new KMailPluginEditorCheckBeforeSendManagerInterface(this);
0261     mPluginEditorInitManagerInterface = new KMailPluginEditorInitManagerInterface(this);
0262     mPluginEditorConvertTextManagerInterface = new KMailPluginEditorConvertTextManagerInterface(this);
0263 
0264     connect(mPluginEditorManagerInterface,
0265             &KMailPluginEditorManagerInterface::errorMessage,
0266             mPluginEditorMessageWidget,
0267             &PimCommon::PurposeMenuMessageWidget::slotShareError);
0268     connect(mPluginEditorManagerInterface,
0269             &KMailPluginEditorManagerInterface::successMessage,
0270             mPluginEditorMessageWidget,
0271             &PimCommon::PurposeMenuMessageWidget::slotShareSuccess);
0272 
0273     connect(mComposerBase, &MessageComposer::ComposerViewBase::disableHtml, this, &KMComposerWin::disableHtml);
0274     connect(mComposerBase, &MessageComposer::ComposerViewBase::enableHtml, this, &KMComposerWin::enableHtml);
0275     connect(mComposerBase, &MessageComposer::ComposerViewBase::failed, this, &KMComposerWin::slotSendFailed);
0276     connect(mComposerBase, &MessageComposer::ComposerViewBase::sentSuccessfully, this, &KMComposerWin::slotSendSuccessful);
0277     connect(mComposerBase, &MessageComposer::ComposerViewBase::modified, this, &KMComposerWin::setModified);
0278 
0279     (void)new MailcomposerAdaptor(this);
0280 
0281     connect(mComposerBase->expiryChecker().get(),
0282             &Kleo::ExpiryChecker::expiryMessage,
0283             this,
0284             [&](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
0285                 Q_UNUSED(isNewMessage);
0286                 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
0287                     const auto plainMsg = msg.replace(QStringLiteral("<p>"), QStringLiteral(" "))
0288                                               .replace(QStringLiteral("</p>"), QStringLiteral(" "))
0289                                               .replace(QStringLiteral("<p align=center>"), QStringLiteral(" "));
0290                     mNearExpiryWarning->addInfo(plainMsg);
0291                     mNearExpiryWarning->setWarning(info == Kleo::ExpiryChecker::OwnKeyExpired);
0292                     mNearExpiryWarning->animatedShow();
0293                 }
0294                 const QList<KPIM::MultiplyingLine *> lstLines = mComposerBase->recipientsEditor()->lines();
0295                 for (KPIM::MultiplyingLine *line : lstLines) {
0296                     auto recipient = line->data().dynamicCast<MessageComposer::Recipient>();
0297                     if (recipient->key().primaryFingerprint() == key.primaryFingerprint()) {
0298                         auto recipientLine = qobject_cast<MessageComposer::RecipientLineNG *>(line);
0299                         QString iconname = QStringLiteral("emblem-warning");
0300                         if (info == Kleo::ExpiryChecker::OtherKeyExpired) {
0301                             mEncryptionState.setAcceptedSolution(false);
0302                             iconname = QStringLiteral("emblem-error");
0303                             const auto showCryptoIndicator = KMailSettings::self()->showCryptoLabelIndicator();
0304                             const auto hasOverride = mEncryptionState.hasOverride();
0305                             const auto encrypt = mEncryptionState.encrypt();
0306 
0307                             const bool showAllIcons = showCryptoIndicator && hasOverride && encrypt;
0308                             if (!showAllIcons) {
0309                                 recipientLine->setIcon(QIcon(), msg);
0310                                 return;
0311                             }
0312                         }
0313 
0314                         recipientLine->setIcon(QIcon::fromTheme(iconname), msg);
0315                         return;
0316                     }
0317                 }
0318             });
0319 
0320     mdbusObjectPath = QLatin1StringView("/Composer_") + QString::number(++s_composerNumber);
0321     QDBusConnection::sessionBus().registerObject(mdbusObjectPath, this);
0322 
0323     auto sigController = new MessageComposer::SignatureController(this);
0324     connect(sigController, &MessageComposer::SignatureController::enableHtml, this, &KMComposerWin::enableHtml);
0325     mComposerBase->setSignatureController(sigController);
0326 
0327     if (!kmkernel->xmlGuiInstanceName().isEmpty()) {
0328         setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2"));
0329     }
0330     mMainWidget = new QWidget(this);
0331     // splitter between the headers area and the actual editor
0332     mHeadersToEditorSplitter = new QSplitter(Qt::Vertical, mMainWidget);
0333     mHeadersToEditorSplitter->setObjectName(QLatin1StringView("mHeadersToEditorSplitter"));
0334     mHeadersToEditorSplitter->setChildrenCollapsible(false);
0335     mHeadersArea = new QWidget(mHeadersToEditorSplitter);
0336     mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding);
0337     mHeadersToEditorSplitter->addWidget(mHeadersArea);
0338     const QList<int> defaultSizes{0};
0339     mHeadersToEditorSplitter->setSizes(defaultSizes);
0340 
0341     auto v = new QVBoxLayout(mMainWidget);
0342     v->setContentsMargins({});
0343     v->addWidget(mHeadersToEditorSplitter);
0344     auto identity = new KIdentityManagementWidgets::IdentityCombo(kmkernel->identityManager(), mHeadersArea);
0345     identity->setCurrentIdentity(mId);
0346     identity->setObjectName(QLatin1StringView("identitycombo"));
0347     connect(identity, &KIdentityManagementWidgets::IdentityCombo::identityDeleted, this, &KMComposerWin::slotIdentityDeleted);
0348     connect(identity, &KIdentityManagementWidgets::IdentityCombo::invalidIdentity, this, &KMComposerWin::slotInvalidIdentity);
0349     mComposerBase->setIdentityCombo(identity);
0350 
0351     sigController->setIdentityCombo(identity);
0352     sigController->suspend(); // we have to do identity change tracking ourselves due to the template code
0353 
0354     auto dictionaryCombo = new DictionaryComboBox(mHeadersArea);
0355     dictionaryCombo->setToolTip(i18n("Select the dictionary to use when spell-checking this message"));
0356     mComposerBase->setDictionary(dictionaryCombo);
0357 
0358     mFccFolder = new MailCommon::FolderRequester(mHeadersArea);
0359     mFccFolder->setNotAllowToCreateNewFolder(true);
0360     mFccFolder->setMustBeReadWrite(true);
0361 
0362     mFccFolder->setToolTip(i18n("Select the sent-mail folder where a copy of this message will be saved"));
0363     connect(mFccFolder, &MailCommon::FolderRequester::folderChanged, this, &KMComposerWin::slotFccFolderChanged);
0364     connect(mFccFolder, &MailCommon::FolderRequester::invalidFolder, this, &KMComposerWin::slotFccIsInvalid);
0365 
0366     auto transport = new MailTransport::TransportComboBox(mHeadersArea);
0367     transport->setToolTip(i18n("Select the outgoing account to use for sending this message"));
0368     mComposerBase->setTransportCombo(transport);
0369     connect(transport, &MailTransport::TransportComboBox::activated, this, &KMComposerWin::slotTransportChanged);
0370     connect(transport, &MailTransport::TransportComboBox::transportRemoved, this, &KMComposerWin::slotTransportRemoved);
0371     mEdtFrom = new MessageComposer::ComposerLineEdit(false, mHeadersArea);
0372     mEdtFrom->installEventFilter(this);
0373     mEdtFrom->setObjectName(QLatin1StringView("fromLine"));
0374     mEdtFrom->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
0375     mEdtFrom->setToolTip(i18n("Set the \"From:\" email address for this message"));
0376 
0377     auto recipientsEditor = new MessageComposer::RecipientsEditor(mHeadersArea);
0378     recipientsEditor->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
0379     connect(recipientsEditor, &MessageComposer::RecipientsEditor::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
0380     connect(recipientsEditor, &MessageComposer::RecipientsEditor::sizeHintChanged, this, &KMComposerWin::recipientEditorSizeHintChanged);
0381     connect(recipientsEditor, &MessageComposer::RecipientsEditor::lineAdded, this, &KMComposerWin::slotRecipientEditorLineAdded);
0382     connect(recipientsEditor, &MessageComposer::RecipientsEditor::focusInRecipientLineEdit, this, &KMComposerWin::slotRecipientEditorLineFocused);
0383     mComposerBase->setRecipientsEditor(recipientsEditor);
0384 
0385     mEdtSubject = new SubjectLineEditWithAutoCorrection(mHeadersArea, QStringLiteral("kmail2rc"));
0386     mEdtSubject->installEventFilter(this);
0387     mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection());
0388     connect(mEdtSubject, &SubjectLineEditWithAutoCorrection::handleMimeData, this, [this](const QMimeData *mimeData) {
0389         insertFromMimeData(mimeData, false);
0390     });
0391 
0392     connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &KMComposerWin::slotEncryptionButtonIconUpdate);
0393     connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &KMComposerWin::updateSignatureAndEncryptionStateIndicators);
0394     connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &KMComposerWin::slotEncryptionButtonIconUpdate);
0395     connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &KMComposerWin::runKeyResolver);
0396     connect(&mEncryptionState, &EncryptionState::acceptedSolutionChanged, this, &KMComposerWin::slotEncryptionButtonIconUpdate);
0397 
0398     mRunKeyResolverTimer = new QTimer(this);
0399     mRunKeyResolverTimer->setSingleShot(true);
0400     mRunKeyResolverTimer->setInterval(500ms);
0401     connect(mRunKeyResolverTimer, &QTimer::timeout, this, &KMComposerWin::runKeyResolver);
0402 
0403     mLblIdentity = new QLabel(i18n("&Identity:"), mHeadersArea);
0404     mDictionaryLabel = new QLabel(i18n("&Dictionary:"), mHeadersArea);
0405     mLblFcc = new QLabel(i18n("&Sent-Mail folder:"), mHeadersArea);
0406     mLblTransport = new QLabel(i18n("&Mail transport:"), mHeadersArea);
0407     mLblFrom = new QLabel(i18nc("sender address field", "&From:"), mHeadersArea);
0408     mLblSubject = new QLabel(i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea);
0409     mShowHeaders = KMailSettings::self()->headers();
0410     mDone = false;
0411     // the attachment view is separated from the editor by a splitter
0412     mSplitter = new QSplitter(Qt::Vertical, mMainWidget);
0413     mSplitter->setObjectName(QLatin1StringView("mSplitter"));
0414     mSplitter->setChildrenCollapsible(false);
0415     mSnippetSplitter = new QSplitter(Qt::Horizontal, mSplitter);
0416     mSnippetSplitter->setObjectName(QLatin1StringView("mSnippetSplitter"));
0417     mSplitter->addWidget(mSnippetSplitter);
0418 
0419     auto editorAndCryptoStateIndicators = new QWidget(mSplitter);
0420 
0421     auto vbox = new QVBoxLayout(editorAndCryptoStateIndicators);
0422     vbox->setSpacing(0);
0423     vbox->setContentsMargins({});
0424 
0425     connect(mPotentialPhishingEmailWarning, &PotentialPhishingEmailWarning::sendNow, this, &KMComposerWin::slotCheckSendNowStep2);
0426     vbox->addWidget(mPotentialPhishingEmailWarning);
0427 
0428     connect(mAttachmentMissing, &AttachmentMissingWarning::attachMissingFile, this, &KMComposerWin::slotAttachMissingFile);
0429     connect(mAttachmentMissing, &AttachmentMissingWarning::explicitClosedMissingAttachment, this, &KMComposerWin::slotExplicitClosedMissingAttachment);
0430     vbox->addWidget(mAttachmentMissing);
0431 
0432     auto composerEditorNg = new KMComposerEditorNg(this, mCryptoStateIndicatorWidget);
0433     composerEditorNg->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
0434     mRichTextEditorWidget = new TextCustomEditor::RichTextEditorWidget(composerEditorNg, mCryptoStateIndicatorWidget);
0435     composerEditorNg->installEventFilter(this);
0436 
0437     connect(composerEditorNg, &KMComposerEditorNg::insertEmoticon, mGlobalAction, &KMComposerGlobalAction::slotInsertEmoticon);
0438     // Don't use new connect api here. It crashes
0439     connect(composerEditorNg, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged()));
0440     connect(composerEditorNg, &KMComposerEditorNg::selectionChanged, this, &KMComposerWin::slotSelectionChanged);
0441     // connect(editor, &KMComposerEditor::textChanged, this, &KMComposeWin::slotEditorTextChanged);
0442     mComposerBase->setEditor(composerEditorNg);
0443 
0444     vbox->addWidget(mIncorrectIdentityFolderWarning);
0445 
0446     vbox->addWidget(mPluginEditorMessageWidget);
0447     vbox->addWidget(mAttachmentFromExternalMissing);
0448     vbox->addWidget(mTooMyRecipientWarning);
0449     vbox->addWidget(mNearExpiryWarning);
0450 
0451     vbox->addWidget(mCryptoStateIndicatorWidget);
0452     vbox->addWidget(mRichTextEditorWidget);
0453 
0454     mSnippetSplitter->insertWidget(0, editorAndCryptoStateIndicators);
0455     mSnippetSplitter->setOpaqueResize(true);
0456     sigController->setEditor(composerEditorNg);
0457 
0458     mHeadersToEditorSplitter->addWidget(mSplitter);
0459     composerEditorNg->setAcceptDrops(true);
0460     connect(sigController,
0461             &MessageComposer::SignatureController::signatureAdded,
0462             mComposerBase->editor()->externalComposer(),
0463             &KPIMTextEdit::RichTextExternalComposer::startExternalEditor);
0464 
0465     connect(dictionaryCombo, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KMComposerWin::slotSpellCheckingLanguage);
0466 
0467     connect(composerEditorNg, &KMComposerEditorNg::languageChanged, this, &KMComposerWin::slotDictionaryLanguageChanged);
0468     connect(composerEditorNg, &KMComposerEditorNg::spellCheckStatus, this, &KMComposerWin::slotSpellCheckingStatus);
0469     connect(composerEditorNg, &KMComposerEditorNg::insertModeChanged, this, &KMComposerWin::slotOverwriteModeChanged);
0470     connect(composerEditorNg, &KMComposerEditorNg::spellCheckingFinished, this, &KMComposerWin::slotDelayedCheckSendNow);
0471     mSnippetWidget = new MailCommon::SnippetTreeView(actionCollection(), mSnippetSplitter);
0472     connect(mSnippetWidget, &MailCommon::SnippetTreeView::insertSnippetInfo, this, &KMComposerWin::insertSnippetInfo);
0473     connect(composerEditorNg, &KMComposerEditorNg::insertSnippet, mSnippetWidget->snippetsManager(), &MailCommon::SnippetsManager::insertSnippet);
0474     mSnippetWidget->setVisible(KMailSettings::self()->showSnippetManager());
0475     mSnippetSplitter->addWidget(mSnippetWidget);
0476     mSnippetSplitter->setCollapsible(0, false);
0477     mSnippetSplitterCollapser = new KSplitterCollapserButton(mSnippetWidget, mSnippetSplitter);
0478     mSnippetSplitterCollapser->setVisible(KMailSettings::self()->showSnippetManager());
0479 
0480     mSplitter->setOpaqueResize(true);
0481 
0482     setWindowTitle(i18nc("@title:window", "Composer"));
0483     setMinimumSize(200, 200);
0484 
0485     mCustomToolsWidget = new PimCommon::CustomToolsWidgetNg(this);
0486     mCustomToolsWidget->initializeView(actionCollection(), PimCommon::CustomToolsPluginManager::self()->pluginsList());
0487     mSplitter->addWidget(mCustomToolsWidget);
0488     connect(mCustomToolsWidget, &PimCommon::CustomToolsWidgetNg::insertText, this, &KMComposerWin::slotInsertShortUrl);
0489 
0490     auto attachmentModel = new MessageComposer::AttachmentModel(this);
0491     auto attachmentView = new KMail::AttachmentView(attachmentModel, mSplitter);
0492     attachmentView->hideIfEmpty();
0493     connect(attachmentView, &KMail::AttachmentView::modified, this, &KMComposerWin::setModified);
0494     auto attachmentController = new KMail::AttachmentController(attachmentModel, attachmentView, this);
0495 
0496     mComposerBase->setAttachmentModel(attachmentModel);
0497     mComposerBase->setAttachmentController(attachmentController);
0498 
0499     if (KMailSettings::self()->showForgottenAttachmentWarning()) {
0500         mVerifyMissingAttachment = new QTimer(this);
0501         mVerifyMissingAttachment->setSingleShot(true);
0502         mVerifyMissingAttachment->setInterval(5s);
0503         connect(mVerifyMissingAttachment, &QTimer::timeout, this, &KMComposerWin::slotVerifyMissingAttachmentTimeout);
0504     }
0505     connect(attachmentController, &KMail::AttachmentController::fileAttached, mAttachmentMissing, &AttachmentMissingWarning::slotFileAttached);
0506 
0507     v->addWidget(mExternalEditorWarning);
0508 
0509     mPluginEditorManagerInterface->setParentWidget(this);
0510     mPluginEditorManagerInterface->setRichTextEditor(mRichTextEditorWidget->editor());
0511     mPluginEditorManagerInterface->setActionCollection(actionCollection());
0512     mPluginEditorManagerInterface->setComposerInterface(mComposerBase);
0513 
0514     mPluginEditorCheckBeforeSendManagerInterface->setParentWidget(this);
0515 
0516     mPluginEditorInitManagerInterface->setParentWidget(this);
0517     mPluginEditorInitManagerInterface->setRichTextEditor(composerEditorNg);
0518 
0519     mPluginEditorConvertTextManagerInterface->setParentWidget(this);
0520     mPluginEditorConvertTextManagerInterface->setActionCollection(actionCollection());
0521     mPluginEditorConvertTextManagerInterface->setRichTextEditor(composerEditorNg);
0522 
0523     mPluginEditorGrammarManagerInterface->setParentWidget(this);
0524     mPluginEditorGrammarManagerInterface->setActionCollection(actionCollection());
0525     mPluginEditorGrammarManagerInterface->setRichTextEditor(composerEditorNg);
0526     mPluginEditorGrammarManagerInterface->setCustomToolsWidget(mCustomToolsWidget);
0527 
0528     setupStatusBar(attachmentView->widget());
0529     setupActions();
0530     setupEditor();
0531     rethinkFields();
0532     readConfig();
0533 
0534     updateSignatureAndEncryptionStateIndicators();
0535 
0536     applyMainWindowSettings(KMKernel::self()->config()->group(QStringLiteral("Composer")));
0537 
0538     mUpdateWindowTitleConnection = connect(mEdtSubject, &PimCommon::LineEditWithAutoCorrection::textChanged, this, &KMComposerWin::slotUpdateWindowTitle);
0539     mIdentityConnection = connect(identity, &KIdentityManagementWidgets::IdentityCombo::identityChanged, this, [this](uint val) {
0540         slotIdentityChanged(val);
0541     });
0542     connect(kmkernel->identityManager(), qOverload<uint>(&KIdentityManagementCore::IdentityManager::changed), this, [this](uint val) {
0543         if (currentIdentity() == val) {
0544             slotIdentityChanged(val);
0545         }
0546     });
0547 
0548     connect(mEdtFrom, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
0549     connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::collectionRemoved, this, &KMComposerWin::slotFolderRemoved);
0550     connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::slotConfigChanged);
0551     connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::runKeyResolver);
0552 
0553     mMainWidget->resize(800, 600);
0554     setCentralWidget(mMainWidget);
0555 
0556     if (KMailSettings::self()->useHtmlMarkup()) {
0557         enableHtml();
0558     } else {
0559         disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
0560     }
0561 
0562     if (KMailSettings::self()->useExternalEditor()) {
0563         composerEditorNg->setUseExternalEditor(true);
0564         composerEditorNg->setExternalEditorPath(KMailSettings::self()->externalEditor());
0565     }
0566 
0567     const QList<KPIM::MultiplyingLine *> lstLines = recipientsEditor->lines();
0568     for (KPIM::MultiplyingLine *line : lstLines) {
0569         slotRecipientEditorLineAdded(line);
0570     }
0571 
0572     if (aMsg) {
0573         setMessage(aMsg, lastSignState, lastEncryptState);
0574     }
0575 
0576     mComposerBase->recipientsEditor()->setFocusBottom();
0577     composerEditorNg->composerActions()->updateActionStates(); // set toolbar buttons to correct values
0578 
0579     mDone = true;
0580 
0581     mDummyComposer = new MessageComposer::Composer(this);
0582     mDummyComposer->globalPart()->setParentWidgetForGui(this);
0583 
0584     KConfigGroup grp(KMKernel::self()->config()->group(QStringLiteral("Composer")));
0585     setAutoSaveSettings(grp, true);
0586     connect(mComposerBase, &MessageComposer::ComposerViewBase::tooManyRecipient, this, &KMComposerWin::slotTooManyRecipients);
0587 }
0588 
0589 KMComposerWin::~KMComposerWin()
0590 {
0591     disconnect(mUpdateWindowTitleConnection);
0592     // When we have a collection set, store the message back to that collection.
0593     // Note that when we save the message or sent it, mFolder is set back to 0.
0594     // So this for example kicks in when opening a draft and then closing the window.
0595     if (mFolder.isValid() && mMsg && isModified()) {
0596         auto saveDraftJob = new SaveDraftJob(mMsg, mFolder);
0597         saveDraftJob->start();
0598     }
0599 
0600     delete mComposerBase;
0601 }
0602 
0603 void KMComposerWin::slotTooManyRecipients(bool b)
0604 {
0605     if (b) {
0606         mTooMyRecipientWarning->animatedShow();
0607     } else {
0608         mTooMyRecipientWarning->animatedHide();
0609     }
0610 }
0611 
0612 void KMComposerWin::slotRecipientEditorLineFocused()
0613 {
0614     mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields);
0615 }
0616 
0617 KMComposerWin::ModeType KMComposerWin::modeType() const
0618 {
0619     return mModeType;
0620 }
0621 
0622 void KMComposerWin::setModeType(KMComposerWin::ModeType modeType)
0623 {
0624     mModeType = modeType;
0625 }
0626 
0627 bool KMComposerWin::eventFilter(QObject *obj, QEvent *event)
0628 {
0629     if (event->type() == QEvent::FocusIn) {
0630         if (obj == mEdtSubject) {
0631             mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::SubjectField);
0632         } else if (obj == mComposerBase->recipientsEditor()) {
0633             mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields);
0634         } else if (obj == mEdtFrom) {
0635             mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields);
0636         }
0637     }
0638     return KMail::Composer::eventFilter(obj, event);
0639 }
0640 
0641 void KMComposerWin::insertSnippetInfo(const MailCommon::SnippetInfo &info)
0642 {
0643     {
0644         if (!info.to.isEmpty()) {
0645             const QStringList lst = KEmailAddress::splitAddressList(info.to);
0646             for (const QString &addr : lst) {
0647                 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::To)) {
0648                     qCWarning(KMAIL_LOG) << "Impossible to add to entry";
0649                 }
0650             }
0651         }
0652     }
0653     {
0654         if (!info.cc.isEmpty()) {
0655             const QStringList lst = KEmailAddress::splitAddressList(info.cc);
0656             for (const QString &addr : lst) {
0657                 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::Cc)) {
0658                     qCWarning(KMAIL_LOG) << "Impossible to add cc entry";
0659                 }
0660             }
0661         }
0662     }
0663     {
0664         if (!info.bcc.isEmpty()) {
0665             const QStringList lst = KEmailAddress::splitAddressList(info.bcc);
0666             for (const QString &addr : lst) {
0667                 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
0668                     qCWarning(KMAIL_LOG) << "Impossible to add bcc entry";
0669                 }
0670             }
0671         }
0672     }
0673     {
0674         if (!info.attachment.isEmpty()) {
0675             const QStringList lst = info.attachment.split(QLatin1Char(','));
0676             for (const QString &attach : lst) {
0677                 auto job = new MessageComposer::ConvertSnippetVariablesJob(this);
0678                 job->setText(attach);
0679                 auto interface = new MessageComposer::ComposerViewInterface(mComposerBase);
0680                 job->setComposerViewInterface(interface);
0681                 connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) {
0682                     if (!str.isEmpty()) {
0683                         const QUrl localUrl = QUrl::fromLocalFile(str);
0684                         AttachmentInfo info;
0685                         info.url = localUrl;
0686                         addAttachment(QList<AttachmentInfo>() << info, false);
0687                     }
0688                 });
0689                 job->start();
0690             }
0691         }
0692     }
0693     {
0694         if (!info.subject.isEmpty()) {
0695             // Convert subject
0696             auto job = new MessageComposer::ConvertSnippetVariablesJob(this);
0697             job->setText(info.subject);
0698             auto interface = new MessageComposer::ComposerViewInterface(mComposerBase);
0699             job->setComposerViewInterface(interface);
0700             connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) {
0701                 if (!str.isEmpty()) {
0702                     if (mComposerBase->subject().isEmpty()) { // Add subject only if we don't have subject
0703                         mEdtSubject->setPlainText(str);
0704                     }
0705                 }
0706             });
0707             job->start();
0708         }
0709     }
0710     {
0711         if (!info.text.isEmpty()) {
0712             // Convert plain text
0713             auto job = new MessageComposer::ConvertSnippetVariablesJob(this);
0714             job->setText(info.text);
0715             auto interface = new MessageComposer::ComposerViewInterface(mComposerBase);
0716             job->setComposerViewInterface(interface);
0717             connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) {
0718                 mComposerBase->editor()->insertPlainText(str);
0719             });
0720             job->start();
0721         }
0722     }
0723 }
0724 
0725 void KMComposerWin::slotSpellCheckingLanguage(const QString &language)
0726 {
0727     mComposerBase->editor()->setSpellCheckingLanguage(language);
0728     mEdtSubject->setSpellCheckingLanguage(language);
0729 }
0730 
0731 QString KMComposerWin::dbusObjectPath() const
0732 {
0733     return mdbusObjectPath;
0734 }
0735 
0736 void KMComposerWin::slotEditorTextChanged()
0737 {
0738     const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty();
0739     mFindText->setEnabled(textIsNotEmpty);
0740     mFindNextText->setEnabled(textIsNotEmpty);
0741     mReplaceText->setEnabled(textIsNotEmpty);
0742     mSelectAll->setEnabled(textIsNotEmpty);
0743     if (mVerifyMissingAttachment && !mVerifyMissingAttachment->isActive()) {
0744         mVerifyMissingAttachment->start();
0745     }
0746 }
0747 
0748 void KMComposerWin::send(int how)
0749 {
0750     switch (how) {
0751     case 1:
0752         slotSendNow();
0753         break;
0754     default:
0755     case 0:
0756     // TODO: find out, what the default send method is and send it this way
0757     case 2:
0758         slotSendLater();
0759         break;
0760     }
0761 }
0762 
0763 void KMComposerWin::addAttachmentsAndSend(const QList<QUrl> &urls, const QString &comment, int how)
0764 {
0765     const int nbUrl = urls.count();
0766     for (int i = 0; i < nbUrl; ++i) {
0767         mComposerBase->addAttachment(urls.at(i), comment, true);
0768     }
0769 
0770     send(how);
0771 }
0772 
0773 void KMComposerWin::addAttachment(const QList<KMail::Composer::AttachmentInfo> &infos, bool showWarning)
0774 {
0775     QStringList lst;
0776     for (const AttachmentInfo &info : infos) {
0777         if (showWarning) {
0778             lst.append(info.url.toDisplayString());
0779         }
0780         mComposerBase->addAttachment(info.url, info.comment, false);
0781     }
0782     if (showWarning) {
0783         mAttachmentFromExternalMissing->setAttachmentNames(lst);
0784         mAttachmentFromExternalMissing->animatedShow();
0785     }
0786 }
0787 
0788 void KMComposerWin::addAttachment(const QString &name,
0789                                   KMime::Headers::contentEncoding cte,
0790                                   const QString &charset,
0791                                   const QByteArray &data,
0792                                   const QByteArray &mimeType)
0793 {
0794     Q_UNUSED(cte)
0795     mComposerBase->addAttachment(name, name, charset, data, mimeType);
0796 }
0797 
0798 void KMComposerWin::readConfig(bool reload)
0799 {
0800     mEdtFrom->setCompletionMode(static_cast<KCompletion::CompletionMode>(KMailSettings::self()->completionMode()));
0801     mComposerBase->recipientsEditor()->setCompletionMode(static_cast<KCompletion::CompletionMode>(KMailSettings::self()->completionMode()));
0802     mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator());
0803 
0804     if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
0805         mBodyFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
0806         mFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
0807     } else {
0808         mBodyFont = KMailSettings::self()->composerFont();
0809         mFixedFont = MessageViewer::MessageViewerSettings::self()->fixedFont();
0810     }
0811 
0812     slotUpdateFont();
0813     mEdtFrom->setFont(mBodyFont);
0814     mEdtSubject->setFont(mBodyFont);
0815 
0816     if (!reload) {
0817         QSize composerSize = KMailSettings::self()->composerSize();
0818         if (composerSize.width() < 200) {
0819             composerSize.setWidth(200);
0820         }
0821         if (composerSize.height() < 200) {
0822             composerSize.setHeight(200);
0823         }
0824         resize(composerSize);
0825 
0826         if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) {
0827             mSnippetSplitter->setSizes(KMailSettings::self()->snippetSplitterPosition());
0828         } else {
0829             const QList<int> defaults{(int)(width() * 0.8), (int)(width() * 0.2)};
0830             mSnippetSplitter->setSizes(defaults);
0831         }
0832     }
0833 
0834     mComposerBase->identityCombo()->setCurrentIdentity(mId);
0835     qCDebug(KMAIL_LOG) << mComposerBase->identityCombo()->currentIdentityName();
0836     const KIdentityManagementCore::Identity &ident = kmkernel->identityManager()->identityForUoid(mId);
0837 
0838     mComposerBase->setAutoSaveInterval(KMailSettings::self()->autosaveInterval() * 1000 * 60);
0839 
0840     mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
0841 
0842     const QString fccName = ident.fcc();
0843     setFcc(fccName);
0844 }
0845 
0846 void KMComposerWin::writeConfig()
0847 {
0848     KMailSettings::self()->setHeaders(mShowHeaders);
0849     KMailSettings::self()->setCurrentTransport(mComposerBase->transportComboBox()->currentText());
0850     KMailSettings::self()->setPreviousIdentity(currentIdentity());
0851     KMailSettings::self()->setPreviousFcc(QString::number(mFccFolder->collection().id()));
0852     KMailSettings::self()->setPreviousDictionary(mComposerBase->dictionary()->currentDictionaryName());
0853     KMailSettings::self()->setAutoSpellChecking(mAutoSpellCheckingAction->isChecked());
0854     MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mFixedFontAction->isChecked());
0855     if (!mForceDisableHtml) {
0856         KMailSettings::self()->setUseHtmlMarkup(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
0857     }
0858     KMailSettings::self()->setComposerSize(size());
0859     KMailSettings::self()->setShowSnippetManager(mSnippetAction->isChecked());
0860 
0861     if (mSnippetAction->isChecked()) {
0862         KMailSettings::setSnippetSplitterPosition(mSnippetSplitter->sizes());
0863     }
0864 
0865     // make sure config changes are written to disk, cf. bug 127538
0866     KMKernel::self()->slotSyncConfig();
0867 }
0868 
0869 MessageComposer::Composer *KMComposerWin::createSimpleComposer()
0870 {
0871     mComposerBase->setFrom(from());
0872     mComposerBase->setSubject(subject());
0873     auto composer = new MessageComposer::Composer();
0874     mComposerBase->fillComposer(composer);
0875     return composer;
0876 }
0877 
0878 bool KMComposerWin::canSignEncryptAttachments() const
0879 {
0880     return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat;
0881 }
0882 
0883 void KMComposerWin::slotUpdateView()
0884 {
0885     if (!mDone) {
0886         return; // otherwise called from rethinkFields during the construction
0887         // which is not the intended behavior
0888     }
0889 
0890     // This sucks awfully, but no, I cannot get an activated(int id) from
0891     // actionContainer()
0892     auto act = ::qobject_cast<KToggleAction *>(sender());
0893     if (!act) {
0894         return;
0895     }
0896     int id;
0897 
0898     if (act == mAllFieldsAction) {
0899         id = 0;
0900     } else if (act == mIdentityAction) {
0901         id = HDR_IDENTITY;
0902     } else if (act == mTransportAction) {
0903         id = HDR_TRANSPORT;
0904     } else if (act == mFromAction) {
0905         id = HDR_FROM;
0906     } else if (act == mSubjectAction) {
0907         id = HDR_SUBJECT;
0908     } else if (act == mFccAction) {
0909         id = HDR_FCC;
0910     } else if (act == mDictionaryAction) {
0911         id = HDR_DICTIONARY;
0912     } else {
0913         qCDebug(KMAIL_LOG) << "Something is wrong (Oh, yeah?)";
0914         return;
0915     }
0916 
0917     bool forceAllHeaders = false;
0918     // sanders There's a bug here this logic doesn't work if no
0919     // fields are shown and then show all fields is selected.
0920     // Instead of all fields being shown none are.
0921     if (!act->isChecked()) {
0922         // hide header
0923         if (id > 0) {
0924             mShowHeaders = mShowHeaders & ~id;
0925         } else {
0926             mShowHeaders = std::abs(mShowHeaders);
0927         }
0928     } else {
0929         // show header
0930         if (id > 0) {
0931             mShowHeaders |= id;
0932         } else {
0933             mShowHeaders = -std::abs(mShowHeaders);
0934             if (mShowHeaders == 0) {
0935                 forceAllHeaders = true;
0936             }
0937         }
0938     }
0939     rethinkFields(true, forceAllHeaders);
0940 }
0941 
0942 int KMComposerWin::calcColumnWidth(int which, long allShowing, int width) const
0943 {
0944     if ((allShowing & which) == 0) {
0945         return width;
0946     }
0947 
0948     QLabel *w = nullptr;
0949     if (which == HDR_IDENTITY) {
0950         w = mLblIdentity;
0951     } else if (which == HDR_DICTIONARY) {
0952         w = mDictionaryLabel;
0953     } else if (which == HDR_FCC) {
0954         w = mLblFcc;
0955     } else if (which == HDR_TRANSPORT) {
0956         w = mLblTransport;
0957     } else if (which == HDR_FROM) {
0958         w = mLblFrom;
0959     } else if (which == HDR_SUBJECT) {
0960         w = mLblSubject;
0961     } else {
0962         return width;
0963     }
0964 
0965     w->setBuddy(mComposerBase->editor()); // set dummy so we don't calculate width of '&' for this label.
0966     w->adjustSize();
0967     w->show();
0968     return qMax(width, w->sizeHint().width());
0969 }
0970 
0971 void KMComposerWin::rethinkFields(bool fromSlot, bool forceAllHeaders)
0972 {
0973     // This sucks even more but again no ids. sorry (sven)
0974     int mask;
0975     int row;
0976     long showHeaders;
0977 
0978     if ((mShowHeaders < 0) || forceAllHeaders) {
0979         showHeaders = HDR_ALL;
0980     } else {
0981         showHeaders = mShowHeaders;
0982     }
0983 
0984     for (mask = 1, mNumHeaders = 0; mask <= showHeaders; mask <<= 1) {
0985         if ((showHeaders & mask) != 0) {
0986             ++mNumHeaders;
0987         }
0988     }
0989     delete mGrid;
0990     mGrid = new QGridLayout(mHeadersArea);
0991     mGrid->setColumnStretch(0, 1);
0992     mGrid->setColumnStretch(1, 100);
0993     mGrid->setRowStretch(mNumHeaders + 1, 100);
0994 
0995     row = 0;
0996 
0997     mLabelWidth = mComposerBase->recipientsEditor()->setFirstColumnWidth(0) + 2;
0998     if (std::abs(showHeaders) & HDR_IDENTITY) {
0999         mLabelWidth = calcColumnWidth(HDR_IDENTITY, showHeaders, mLabelWidth);
1000     }
1001     if (std::abs(showHeaders) & HDR_DICTIONARY) {
1002         mLabelWidth = calcColumnWidth(HDR_DICTIONARY, showHeaders, mLabelWidth);
1003     }
1004     if (std::abs(showHeaders) & HDR_FCC) {
1005         mLabelWidth = calcColumnWidth(HDR_FCC, showHeaders, mLabelWidth);
1006     }
1007     if (std::abs(showHeaders) & HDR_TRANSPORT) {
1008         mLabelWidth = calcColumnWidth(HDR_TRANSPORT, showHeaders, mLabelWidth);
1009     }
1010     if (std::abs(showHeaders) & HDR_FROM) {
1011         mLabelWidth = calcColumnWidth(HDR_FROM, showHeaders, mLabelWidth);
1012     }
1013     if (std::abs(showHeaders) & HDR_SUBJECT) {
1014         mLabelWidth = calcColumnWidth(HDR_SUBJECT, showHeaders, mLabelWidth);
1015     }
1016 
1017     if (!fromSlot) {
1018         mAllFieldsAction->setChecked(showHeaders == HDR_ALL);
1019     }
1020 
1021     if (!fromSlot) {
1022         mIdentityAction->setChecked(std::abs(mShowHeaders) & HDR_IDENTITY);
1023     }
1024     rethinkHeaderLine(showHeaders, HDR_IDENTITY, row, mLblIdentity, mComposerBase->identityCombo());
1025 
1026     if (!fromSlot) {
1027         mDictionaryAction->setChecked(std::abs(mShowHeaders) & HDR_DICTIONARY);
1028     }
1029     rethinkHeaderLine(showHeaders, HDR_DICTIONARY, row, mDictionaryLabel, mComposerBase->dictionary());
1030 
1031     if (!fromSlot) {
1032         mFccAction->setChecked(std::abs(mShowHeaders) & HDR_FCC);
1033     }
1034     rethinkHeaderLine(showHeaders, HDR_FCC, row, mLblFcc, mFccFolder);
1035 
1036     if (!fromSlot) {
1037         mTransportAction->setChecked(std::abs(mShowHeaders) & HDR_TRANSPORT);
1038     }
1039     rethinkHeaderLine(showHeaders, HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox());
1040 
1041     if (!fromSlot) {
1042         mFromAction->setChecked(std::abs(mShowHeaders) & HDR_FROM);
1043     }
1044     rethinkHeaderLine(showHeaders, HDR_FROM, row, mLblFrom, mEdtFrom);
1045 
1046     mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2);
1047     ++row;
1048     connect(mEdtFrom, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop);
1049     connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtFrom, qOverload<>(&QWidget::setFocus));
1050 
1051     connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusDown, mEdtSubject, qOverload<>(&QWidget::setFocus));
1052     connect(mEdtSubject, &PimCommon::SpellCheckLineEdit::focusUp, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusBottom);
1053 
1054     if (!fromSlot) {
1055         mSubjectAction->setChecked(std::abs(mShowHeaders) & HDR_SUBJECT);
1056     }
1057     rethinkHeaderLine(showHeaders, HDR_SUBJECT, row, mLblSubject, mEdtSubject);
1058     connectFocusMoving(mEdtSubject, mComposerBase->editor());
1059 
1060     assert(row <= mNumHeaders + 1);
1061 
1062     mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
1063 
1064     mIdentityAction->setEnabled(!mAllFieldsAction->isChecked());
1065     mDictionaryAction->setEnabled(!mAllFieldsAction->isChecked());
1066     mTransportAction->setEnabled(!mAllFieldsAction->isChecked());
1067     mFromAction->setEnabled(!mAllFieldsAction->isChecked());
1068     mFccAction->setEnabled(!mAllFieldsAction->isChecked());
1069     mSubjectAction->setEnabled(!mAllFieldsAction->isChecked());
1070     mComposerBase->recipientsEditor()->setFirstColumnWidth(mLabelWidth);
1071 }
1072 
1073 QWidget *KMComposerWin::connectFocusMoving(QWidget *prev, QWidget *next)
1074 {
1075     connect(prev, SIGNAL(focusDown()), next, SLOT(setFocus()));
1076     connect(next, SIGNAL(focusUp()), prev, SLOT(setFocus()));
1077 
1078     return next;
1079 }
1080 
1081 void KMComposerWin::rethinkHeaderLine(int aValue, int aMask, int &aRow, QLabel *aLbl, QWidget *aCbx)
1082 {
1083     if (aValue & aMask) {
1084         aLbl->setBuddy(aCbx);
1085         aLbl->setFixedWidth(mLabelWidth);
1086         mGrid->addWidget(aLbl, aRow, 0);
1087 
1088         mGrid->addWidget(aCbx, aRow, 1);
1089         aCbx->show();
1090         aLbl->show();
1091         aRow++;
1092     } else {
1093         aLbl->hide();
1094         aCbx->hide();
1095     }
1096 }
1097 
1098 void KMComposerWin::slotUpdateComposer(const KIdentityManagementCore::Identity &ident, const KMime::Message::Ptr &msg, uint uoid, uint uoldId, bool wasModified)
1099 {
1100     mComposerBase->updateTemplate(msg);
1101     updateSignature(uoid, uoldId);
1102     updateComposerAfterIdentityChanged(ident, uoid, wasModified);
1103 }
1104 
1105 void KMComposerWin::applyTemplate(uint uoid, uint uOldId, const KIdentityManagementCore::Identity &ident, bool wasModified)
1106 {
1107     TemplateParser::TemplateParserJob::Mode mode;
1108     switch (mContext) {
1109     case New:
1110         mode = TemplateParser::TemplateParserJob::NewMessage;
1111         break;
1112     case Reply:
1113         mode = TemplateParser::TemplateParserJob::Reply;
1114         break;
1115     case ReplyToAll:
1116         mode = TemplateParser::TemplateParserJob::ReplyAll;
1117         break;
1118     case Forward:
1119         mode = TemplateParser::TemplateParserJob::Forward;
1120         break;
1121     case NoTemplate:
1122         updateComposerAfterIdentityChanged(ident, uoid, wasModified);
1123         return;
1124     }
1125 
1126     auto header = new KMime::Headers::Generic("X-KMail-Templates");
1127     header->fromUnicodeString(ident.templates(), "utf-8");
1128     mMsg->setHeader(header);
1129 
1130     if (mode == TemplateParser::TemplateParserJob::NewMessage) {
1131         auto job = new KMComposerUpdateTemplateJob;
1132         connect(job, &KMComposerUpdateTemplateJob::updateComposer, this, &KMComposerWin::slotUpdateComposer);
1133         job->setMsg(mMsg);
1134         job->setCustomTemplate(mCustomTemplate);
1135         job->setTextSelection(mTextSelection);
1136         job->setWasModified(wasModified);
1137         job->setUoldId(uOldId);
1138         job->setUoid(uoid);
1139         job->setIdent(ident);
1140         job->setCollection(mCollectionForNewMessage);
1141         job->start();
1142     } else {
1143         if (auto hrd = mMsg->headerByType("X-KMail-Link-Message")) {
1144             Akonadi::Item::List items;
1145             const QStringList serNums = hrd->asUnicodeString().split(QLatin1Char(','));
1146             items.reserve(serNums.count());
1147             for (const QString &serNumStr : serNums) {
1148                 items << Akonadi::Item(serNumStr.toLongLong());
1149             }
1150 
1151             auto job = new Akonadi::ItemFetchJob(items, this);
1152             job->fetchScope().fetchFullPayload(true);
1153             job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1154             job->setProperty("mode", static_cast<int>(mode));
1155             job->setProperty("uoid", uoid);
1156             job->setProperty("uOldid", uOldId);
1157             connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDelayedApplyTemplate);
1158         }
1159         updateComposerAfterIdentityChanged(ident, uoid, wasModified);
1160     }
1161 }
1162 
1163 void KMComposerWin::slotDelayedApplyTemplate(KJob *job)
1164 {
1165     const Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
1166     // const Akonadi::Item::List items = fetchJob->items();
1167 
1168     // Readd ? const TemplateParser::TemplateParserJob::Mode mode = static_cast<TemplateParser::TemplateParserJob::Mode>(fetchJob->property("mode").toInt());
1169     const uint uoid = fetchJob->property("uoid").toUInt();
1170     const uint uOldId = fetchJob->property("uOldid").toUInt();
1171 #if 0 // FIXME template
1172     TemplateParser::TemplateParser parser(mMsg, mode);
1173     parser.setSelection(mTextSelection);
1174     parser.setAllowDecryption(true);
1175     parser.setWordWrap(MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
1176     parser.setIdentityManager(KMKernel::self()->identityManager());
1177     for (const Akonadi::Item &item : items) {
1178         if (!mCustomTemplate.isEmpty()) {
1179             parser.process(mCustomTemplate, MessageCore::Util::message(item));
1180         } else {
1181             parser.processWithIdentity(uoid, MessageCore::Util::message(item));
1182         }
1183     }
1184 #else
1185     mComposerBase->updateTemplate(mMsg);
1186     updateSignature(uoid, uOldId);
1187     qCWarning(KMAIL_LOG) << " void KMComposerWin::slotDelayedApplyTemplate(KJob *job) is not implemented after removing qtwebkit";
1188 #endif
1189 }
1190 
1191 void KMComposerWin::updateSignature(uint uoid, uint uOldId)
1192 {
1193     const KIdentityManagementCore::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid);
1194     const KIdentityManagementCore::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid(uOldId);
1195     mComposerBase->identityChanged(ident, oldIdentity, true);
1196 }
1197 
1198 void KMComposerWin::setCollectionForNewMessage(const Akonadi::Collection &folder)
1199 {
1200     mCollectionForNewMessage = folder;
1201 }
1202 
1203 void KMComposerWin::setQuotePrefix(uint uoid)
1204 {
1205     QString quotePrefix;
1206     if (auto hrd = mMsg->headerByType("X-KMail-QuotePrefix")) {
1207         quotePrefix = hrd->asUnicodeString();
1208     }
1209     if (quotePrefix.isEmpty()) {
1210         // no quote prefix header, set quote prefix according in identity
1211         // TODO port templates to ComposerViewBase
1212 
1213         if (mCustomTemplate.isEmpty()) {
1214             const KIdentityManagementCore::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault(uoid);
1215             // Get quote prefix from template
1216             // ( custom templates don't specify custom quotes prefixes )
1217             TemplateParser::Templates quoteTemplate(TemplateParser::TemplatesConfiguration::configIdString(identity.uoid()));
1218             quotePrefix = quoteTemplate.quoteString();
1219         }
1220     }
1221     mComposerBase->editor()->setQuotePrefixName(MessageCore::StringUtil::formatQuotePrefix(quotePrefix, mMsg->from()->displayString()));
1222 }
1223 
1224 void KMComposerWin::setupActions()
1225 {
1226     KActionMenuTransport *actActionNowMenu = nullptr;
1227     KActionMenuTransport *actActionLaterMenu = nullptr;
1228 
1229     if (MessageComposer::MessageComposerSettings::self()->sendImmediate()) {
1230         // default = send now, alternative = queue
1231         auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
1232         actionCollection()->addAction(QStringLiteral("send_default"), action);
1233         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return));
1234         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNowByShortcut);
1235 
1236         actActionNowMenu = new KActionMenuTransport(this);
1237         actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
1238         actActionNowMenu->setText(i18n("&Send Mail Via"));
1239 
1240         actActionNowMenu->setIconText(i18n("Send"));
1241         actionCollection()->addAction(QStringLiteral("send_default_via"), actActionNowMenu);
1242 
1243         action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this);
1244         actionCollection()->addAction(QStringLiteral("send_alternative"), action);
1245         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1246 
1247         actActionLaterMenu = new KActionMenuTransport(this);
1248         actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue")));
1249         actActionLaterMenu->setText(i18n("Send &Later Via"));
1250 
1251         actActionLaterMenu->setIconText(i18nc("Queue the message for sending at a later date", "Queue"));
1252         actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionLaterMenu);
1253     } else {
1254         // default = queue, alternative = send now
1255         auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this);
1256         actionCollection()->addAction(QStringLiteral("send_default"), action);
1257         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1258         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return));
1259 
1260         actActionLaterMenu = new KActionMenuTransport(this);
1261         actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue")));
1262         actActionLaterMenu->setText(i18n("Send &Later Via"));
1263         actionCollection()->addAction(QStringLiteral("send_default_via"), actActionLaterMenu);
1264 
1265         action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
1266         actionCollection()->addAction(QStringLiteral("send_alternative"), action);
1267         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNow);
1268 
1269         actActionNowMenu = new KActionMenuTransport(this);
1270         actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
1271         actActionNowMenu->setText(i18n("&Send Mail Via"));
1272         actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionNowMenu);
1273     }
1274 
1275     connect(actActionNowMenu, &QAction::triggered, this, &KMComposerWin::slotSendNow);
1276     connect(actActionLaterMenu, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1277     connect(actActionNowMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendNowVia);
1278     connect(actActionLaterMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendLaterVia);
1279 
1280     auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this);
1281     actionCollection()->addAction(QStringLiteral("save_in_drafts"), action);
1282     KMail::Util::addQActionHelpText(action, i18n("Save email in Draft folder"));
1283     actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_S));
1284     connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveDraft);
1285 
1286     action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Template"), this);
1287     KMail::Util::addQActionHelpText(action, i18n("Save email in Template folder"));
1288     actionCollection()->addAction(QStringLiteral("save_in_templates"), action);
1289     connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveTemplate);
1290 
1291     action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this);
1292     KMail::Util::addQActionHelpText(action, i18n("Save email as text or html file"));
1293     actionCollection()->addAction(QStringLiteral("save_as_file"), action);
1294     connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveAsFile);
1295 
1296     action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this);
1297     actionCollection()->addAction(QStringLiteral("insert_file"), action);
1298     connect(action, &QAction::triggered, this, &KMComposerWin::slotInsertFile);
1299 
1300     mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this);
1301     actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction);
1302     connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &KMComposerWin::slotInsertRecentFile);
1303     connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &KMComposerWin::slotRecentListFileClear);
1304 
1305     const QStringList urls = KMailSettings::self()->recentUrls();
1306     for (const QString &url : urls) {
1307         mRecentAction->addUrl(QUrl(url));
1308     }
1309 
1310     action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this);
1311     KMail::Util::addQActionHelpText(action, i18n("Open Address Book"));
1312     actionCollection()->addAction(QStringLiteral("addressbook"), action);
1313     if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) {
1314         action->setEnabled(false);
1315     } else {
1316         connect(action, &QAction::triggered, this, &KMComposerWin::slotAddressBook);
1317     }
1318     action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Composer"), this);
1319     actionCollection()->addAction(QStringLiteral("new_composer"), action);
1320 
1321     connect(action, &QAction::triggered, this, &KMComposerWin::slotNewComposer);
1322     actionCollection()->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::New));
1323 
1324     action = new QAction(i18n("Select &Recipients..."), this);
1325     actionCollection()->addAction(QStringLiteral("select_recipients"), action);
1326     connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::selectRecipients);
1327     action = new QAction(i18n("Save &Distribution List..."), this);
1328     actionCollection()->addAction(QStringLiteral("save_distribution_list"), action);
1329     connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::saveDistributionList);
1330 
1331     KStandardAction::print(this, &KMComposerWin::slotPrint, actionCollection());
1332     KStandardAction::printPreview(this, &KMComposerWin::slotPrintPreview, actionCollection());
1333     KStandardAction::close(this, &KMComposerWin::slotClose, actionCollection());
1334 
1335     KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection());
1336     KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection());
1337     KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection());
1338     KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection());
1339     KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection());
1340     mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection());
1341 
1342     mFindText = KStandardAction::find(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFind, actionCollection());
1343     mFindNextText = KStandardAction::findNext(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFindNext, actionCollection());
1344 
1345     mReplaceText = KStandardAction::replace(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotReplace, actionCollection());
1346     actionCollection()->addAction(KStandardAction::Spelling, QStringLiteral("spellcheck"), mComposerBase->editor(), SLOT(slotCheckSpelling()));
1347 
1348     action = new QAction(i18n("Paste as Attac&hment"), this);
1349     actionCollection()->addAction(QStringLiteral("paste_att"), action);
1350     connect(action, &QAction::triggered, this, &KMComposerWin::slotPasteAsAttachment);
1351 
1352     action = new QAction(i18n("Cl&ean Spaces"), this);
1353     actionCollection()->addAction(QStringLiteral("clean_spaces"), action);
1354     connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace);
1355 
1356     mFixedFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this);
1357     actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction);
1358     connect(mFixedFontAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateFont);
1359     mFixedFontAction->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont());
1360 
1361     // these are checkable!!!
1362     mUrgentAction = new KToggleAction(i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this);
1363     actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction);
1364     mRequestMDNAction = new KToggleAction(i18n("&Request Disposition Notification"), this);
1365     actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction);
1366     mRequestMDNAction->setChecked(KMailSettings::self()->requestMDN());
1367 
1368     mRequestDeliveryConfirmation = new KToggleAction(i18n("&Request Delivery Confirmation"), this);
1369     actionCollection()->addAction(QStringLiteral("options_request_delivery_confirmation"), mRequestDeliveryConfirmation);
1370     // TOOD mRequestDeliveryConfirmation->setChecked(KMailSettings::self()->requestMDN());
1371 
1372     //----- Message-Encoding Submenu
1373     mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this);
1374     actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction);
1375     mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap());
1376     connect(mWordWrapAction, &KToggleAction::toggled, this, &KMComposerWin::slotWordWrapToggled);
1377 
1378     mSnippetAction = new KToggleAction(i18n("&Snippets"), this);
1379     actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction);
1380     connect(mSnippetAction, &KToggleAction::toggled, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged);
1381     mSnippetAction->setChecked(KMailSettings::self()->showSnippetManager());
1382 
1383     mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this);
1384     actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction);
1385     const bool spellChecking = KMailSettings::self()->autoSpellChecking();
1386     const bool useKmailEditor = !KMailSettings::self()->useExternalEditor();
1387     const bool spellCheckingEnabled = useKmailEditor && spellChecking;
1388     mAutoSpellCheckingAction->setEnabled(useKmailEditor);
1389 
1390     mAutoSpellCheckingAction->setChecked(spellCheckingEnabled);
1391     slotAutoSpellCheckingToggled(spellCheckingEnabled);
1392     connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1393     connect(mComposerBase->editor(), &TextCustomEditor::RichTextEditor::checkSpellingChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1394 
1395     connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::textModeChanged, this, &KMComposerWin::slotTextModeChanged);
1396     connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorClosed, this, &KMComposerWin::slotExternalEditorClosed);
1397     connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorStarted, this, &KMComposerWin::slotExternalEditorStarted);
1398     // these are checkable!!!
1399     mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this);
1400     mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
1401     mMarkupAction->setIconText(i18n("Rich Text"));
1402     mMarkupAction->setToolTip(i18n("Toggle rich text editing mode"));
1403     actionCollection()->addAction(QStringLiteral("html"), mMarkupAction);
1404     connect(mMarkupAction, &KToggleAction::triggered, this, &KMComposerWin::slotToggleMarkup);
1405 
1406     mAllFieldsAction = new KToggleAction(i18n("&All Fields"), this);
1407     actionCollection()->addAction(QStringLiteral("show_all_fields"), mAllFieldsAction);
1408     connect(mAllFieldsAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1409     mIdentityAction = new KToggleAction(i18n("&Identity"), this);
1410     actionCollection()->addAction(QStringLiteral("show_identity"), mIdentityAction);
1411     connect(mIdentityAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1412     mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this);
1413     actionCollection()->addAction(QStringLiteral("show_dictionary"), mDictionaryAction);
1414     connect(mDictionaryAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1415     mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this);
1416     actionCollection()->addAction(QStringLiteral("show_fcc"), mFccAction);
1417     connect(mFccAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1418     mTransportAction = new KToggleAction(i18n("&Mail Transport"), this);
1419     actionCollection()->addAction(QStringLiteral("show_transport"), mTransportAction);
1420     connect(mTransportAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1421     mFromAction = new KToggleAction(i18n("&From"), this);
1422     actionCollection()->addAction(QStringLiteral("show_from"), mFromAction);
1423     connect(mFromAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1424     mSubjectAction = new KToggleAction(i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this);
1425     actionCollection()->addAction(QStringLiteral("show_subject"), mSubjectAction);
1426     connect(mSubjectAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1427     // end of checkable
1428 
1429     mAppendSignature = new QAction(i18n("Append S&ignature"), this);
1430     actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature);
1431     connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1432 
1433     mPrependSignature = new QAction(i18n("Pr&epend Signature"), this);
1434     actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature);
1435     connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1436 
1437     mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this);
1438     actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition);
1439     connect(mInsertSignatureAtCursorPosition,
1440             &QAction::triggered,
1441             mComposerBase->signatureController(),
1442             &MessageComposer::SignatureController::insertSignatureAtCursor);
1443 
1444     mComposerBase->attachmentController()->createActions();
1445 
1446     setStandardToolBarMenuEnabled(true);
1447 
1448     KStandardAction::keyBindings(this, &KMComposerWin::slotEditKeys, actionCollection());
1449     KStandardAction::configureToolbars(this, &KMComposerWin::slotEditToolbars, actionCollection());
1450     KStandardAction::preferences(kmkernel, &KMKernel::slotShowConfigurationDialog, actionCollection());
1451 
1452     action = new QAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Spellchecker..."), this);
1453     action->setIconText(i18n("Spellchecker"));
1454     actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action);
1455     connect(action, &QAction::triggered, this, &KMComposerWin::slotSpellcheckConfig);
1456 
1457     mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this);
1458     mEncryptAction->setIconText(i18n("Encrypt"));
1459     actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction);
1460     mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this);
1461     mSignAction->setIconText(i18n("Sign"));
1462     actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction);
1463     const auto ident = identity();
1464     // PENDING(marc): check the uses of this member and split it into
1465     // smime/openpgp and or enc/sign, if necessary:
1466     mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1467     mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1468 
1469     mLastEncryptActionState = false;
1470     mLastSignActionState = pgpAutoSign();
1471 
1472     changeCryptoAction();
1473 
1474     connect(&mEncryptionState, &EncryptionState::possibleEncryptChanged, mEncryptAction, &KToggleAction::setEnabled);
1475     connect(&mEncryptionState, &EncryptionState::encryptChanged, mEncryptAction, &KToggleAction::setChecked);
1476     connect(mEncryptAction, &KToggleAction::triggered, &mEncryptionState, &EncryptionState::toggleOverride);
1477     connect(mSignAction, &KToggleAction::triggered, this, &KMComposerWin::slotSignToggled);
1478 
1479     QStringList listCryptoFormat;
1480     listCryptoFormat.reserve(numCryptoMessageFormats);
1481     for (int i = 0; i < numCryptoMessageFormats; ++i) {
1482         listCryptoFormat.push_back(Kleo::cryptoMessageFormatToLabel(cryptoMessageFormats[i]));
1483     }
1484 
1485     mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this);
1486     actionCollection()->addAction(QStringLiteral("options_select_crypto"), mCryptoModuleAction);
1487     connect(mCryptoModuleAction, &KSelectAction::indexTriggered, this, &KMComposerWin::slotCryptoModuleSelected);
1488     mCryptoModuleAction->setToolTip(i18n("Select a cryptographic format for this message"));
1489     mCryptoModuleAction->setItems(listCryptoFormat);
1490 
1491     mComposerBase->editor()->createActions(actionCollection());
1492 
1493     mFollowUpToggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("Create Follow Up Reminder..."), this);
1494     actionCollection()->addAction(QStringLiteral("follow_up_mail"), mFollowUpToggleAction);
1495     connect(mFollowUpToggleAction, &KToggleAction::triggered, this, &KMComposerWin::slotFollowUpMail);
1496     mFollowUpToggleAction->setEnabled(MessageComposer::FollowUpReminder::isAvailableAndEnabled());
1497 
1498     mPluginEditorManagerInterface->initializePlugins();
1499     mPluginEditorCheckBeforeSendManagerInterface->initializePlugins();
1500     mPluginEditorInitManagerInterface->initializePlugins();
1501     mPluginEditorConvertTextManagerInterface->initializePlugins();
1502     mPluginEditorGrammarManagerInterface->initializePlugins();
1503 
1504     mShowMenuBarAction = KStandardAction::showMenubar(this, &KMComposerWin::slotToggleMenubar, actionCollection());
1505     mShowMenuBarAction->setChecked(KMailSettings::self()->composerShowMenuBar());
1506     slotToggleMenubar(true);
1507 
1508     mHamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
1509     mHamburgerMenu->setShowMenuBarAction(mShowMenuBarAction);
1510     mHamburgerMenu->setMenuBar(menuBar());
1511     connect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, [this]() {
1512         updateHamburgerMenu();
1513         // Immediately disconnect. We only need to run this once, but on demand.
1514         // NOTE: The nullptr at the end disconnects all connections between
1515         // q and mHamburgerMenu's aboutToShowMenu signal.
1516         disconnect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, nullptr);
1517     });
1518 
1519     createGUI(QStringLiteral("kmcomposerui.rc"));
1520     initializePluginActions();
1521     connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &KMComposerWin::htmlToolBarVisibilityChanged);
1522 
1523     // In Kontact, this entry would read "Configure Kontact", but bring
1524     // up KMail's config dialog. That's sensible, though, so fix the label.
1525     QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure"));
1526     if (configureAction) {
1527         configureAction->setText(i18n("Configure KMail..."));
1528     }
1529 }
1530 
1531 void KMComposerWin::updateHamburgerMenu()
1532 {
1533     auto menu = new QMenu(this);
1534     menu->addAction(actionCollection()->action(QStringLiteral("new_composer")));
1535     menu->addSeparator();
1536     menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Undo)));
1537     menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Redo)));
1538     menu->addSeparator();
1539     menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Print)));
1540     menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::PrintPreview)));
1541     menu->addSeparator();
1542     menu->addAction(actionCollection()->action(QStringLiteral("attach_menu")));
1543     menu->addSeparator();
1544     menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Close)));
1545     mHamburgerMenu->setMenu(menu);
1546 }
1547 
1548 void KMComposerWin::slotToggleMenubar(bool dontShowWarning)
1549 {
1550     if (menuBar()) {
1551         if (mShowMenuBarAction->isChecked()) {
1552             menuBar()->show();
1553         } else {
1554             if (!dontShowWarning && (!toolBar()->isVisible() || !toolBar()->actions().contains(mHamburgerMenu))) {
1555                 const QString accel = mShowMenuBarAction->shortcut().toString();
1556                 KMessageBox::information(this,
1557                                          i18n("<qt>This will hide the menu bar completely."
1558                                               " You can show it again by typing %1.</qt>",
1559                                               accel),
1560                                          i18nc("@title:window", "Hide menu bar"),
1561                                          QStringLiteral("HideMenuBarWarning"));
1562             }
1563             menuBar()->hide();
1564         }
1565         KMailSettings::self()->setComposerShowMenuBar(mShowMenuBarAction->isChecked());
1566     }
1567 }
1568 
1569 void KMComposerWin::initializePluginActions()
1570 {
1571     if (guiFactory()) {
1572         QHash<QString, QList<QAction *>> hashActions;
1573         QHashIterator<MessageComposer::PluginActionType::Type, QList<QAction *>> localEditorManagerActionsType(mPluginEditorManagerInterface->actionsType());
1574         while (localEditorManagerActionsType.hasNext()) {
1575             localEditorManagerActionsType.next();
1576             QList<QAction *> lst = localEditorManagerActionsType.value();
1577             if (!lst.isEmpty()) {
1578                 const QString actionlistname =
1579                     QLatin1StringView("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorManagerActionsType.key());
1580                 hashActions.insert(actionlistname, lst);
1581             }
1582         }
1583         QHashIterator<MessageComposer::PluginActionType::Type, QList<QAction *>> localEditorConvertTextManagerActionsType(
1584             mPluginEditorConvertTextManagerInterface->actionsType());
1585         while (localEditorConvertTextManagerActionsType.hasNext()) {
1586             localEditorConvertTextManagerActionsType.next();
1587             QList<QAction *> lst = localEditorConvertTextManagerActionsType.value();
1588             if (!lst.isEmpty()) {
1589                 const QString actionlistname =
1590                     QLatin1StringView("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorConvertTextManagerActionsType.key());
1591                 if (hashActions.contains(actionlistname)) {
1592                     lst = hashActions.value(actionlistname) + lst;
1593                     hashActions.remove(actionlistname);
1594                 }
1595                 hashActions.insert(actionlistname, lst);
1596             }
1597         }
1598 
1599         const QList<KToggleAction *> customToolsWidgetActionList = mCustomToolsWidget->actionList();
1600         const QString actionlistname =
1601             QLatin1StringView("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(MessageComposer::PluginActionType::Tools);
1602         for (KToggleAction *act : customToolsWidgetActionList) {
1603             QList<QAction *> lst{act};
1604             if (hashActions.contains(actionlistname)) {
1605                 lst = hashActions.value(actionlistname) + lst;
1606                 hashActions.remove(actionlistname);
1607             }
1608             hashActions.insert(actionlistname, lst);
1609         }
1610 
1611         QHash<QString, QList<QAction *>>::const_iterator i = hashActions.constBegin();
1612 
1613         while (i != hashActions.constEnd()) {
1614             const auto lst = guiFactory()->clients();
1615             for (KXMLGUIClient *client : lst) {
1616                 client->unplugActionList(i.key());
1617                 client->plugActionList(i.key(), i.value());
1618             }
1619             ++i;
1620         }
1621         // Initialize statusbar
1622         const QList<QWidget *> statusbarWidgetList = mPluginEditorManagerInterface->statusBarWidgetList();
1623         for (int index = 0; index < statusbarWidgetList.count(); ++index) {
1624             statusBar()->addPermanentWidget(statusbarWidgetList.at(index), 0);
1625         }
1626         const QList<QWidget *> statusbarWidgetListConverter = mPluginEditorConvertTextManagerInterface->statusBarWidgetList();
1627         for (int index = 0; index < statusbarWidgetListConverter.count(); ++index) {
1628             statusBar()->addPermanentWidget(statusbarWidgetListConverter.at(index), 0);
1629         }
1630     }
1631 }
1632 
1633 void KMComposerWin::changeCryptoAction()
1634 {
1635     const auto ident = identity();
1636 
1637     if (!QGpgME::openpgp() && !QGpgME::smime()) {
1638         // no crypto whatsoever
1639         mEncryptAction->setEnabled(false);
1640         mEncryptionState.setPossibleEncrypt(false);
1641         mSignAction->setEnabled(false);
1642         setSigning(false);
1643     } else {
1644         const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty();
1645         const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty();
1646 
1647         setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign());
1648     }
1649 }
1650 
1651 void KMComposerWin::setupStatusBar(QWidget *w)
1652 {
1653     statusBar()->addWidget(w);
1654     mStatusbarLabel = new QLabel(this);
1655     mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
1656     statusBar()->addPermanentWidget(mStatusbarLabel);
1657 
1658     mCursorLineLabel = new QLabel(this);
1659     mCursorLineLabel->setTextFormat(Qt::PlainText);
1660     mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral("     ")));
1661     statusBar()->addPermanentWidget(mCursorLineLabel);
1662 
1663     mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral("     ")));
1664     mCursorColumnLabel->setTextFormat(Qt::PlainText);
1665     statusBar()->addPermanentWidget(mCursorColumnLabel);
1666 
1667     mStatusBarLabelToggledOverrideMode = new MessageComposer::StatusBarLabelToggledState(this);
1668     mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS"));
1669     statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0);
1670     connect(mStatusBarLabelToggledOverrideMode,
1671             &MessageComposer::StatusBarLabelToggledState::toggleModeChanged,
1672             this,
1673             &KMComposerWin::slotOverwriteModeWasChanged);
1674 
1675     mStatusBarLabelSpellCheckingChangeMode = new MessageComposer::StatusBarLabelToggledState(this);
1676     mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off"));
1677     statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0);
1678     connect(mStatusBarLabelSpellCheckingChangeMode,
1679             &MessageComposer::StatusBarLabelToggledState::toggleModeChanged,
1680             this,
1681             &KMComposerWin::slotAutoSpellCheckingToggled);
1682 }
1683 
1684 void KMComposerWin::setupEditor()
1685 {
1686     QFontMetrics fm(mBodyFont);
1687     mComposerBase->editor()->setTabStopDistance(fm.boundingRect(QLatin1Char(' ')).width() * 8);
1688 
1689     slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap());
1690 
1691     // Font setup
1692     slotUpdateFont();
1693 
1694     connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &KMComposerWin::slotCursorPositionChanged);
1695     slotCursorPositionChanged();
1696 }
1697 
1698 QString KMComposerWin::subject() const
1699 {
1700     return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText());
1701 }
1702 
1703 QString KMComposerWin::from() const
1704 {
1705     return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text());
1706 }
1707 
1708 void KMComposerWin::slotInvalidIdentity()
1709 {
1710     mIncorrectIdentityFolderWarning->identityInvalid();
1711 }
1712 
1713 void KMComposerWin::slotFccIsInvalid()
1714 {
1715     mIncorrectIdentityFolderWarning->fccIsInvalid();
1716 }
1717 
1718 void KMComposerWin::setCurrentTransport(int transportId)
1719 {
1720     if (!mComposerBase->transportComboBox()->setCurrentTransport(transportId)) {
1721         mIncorrectIdentityFolderWarning->mailTransportIsInvalid();
1722     }
1723 }
1724 
1725 uint KMComposerWin::currentIdentity() const
1726 {
1727     return mComposerBase->identityCombo()->currentIdentity();
1728 }
1729 
1730 void KMComposerWin::addFaceHeaders(const KIdentityManagementCore::Identity &ident, const KMime::Message::Ptr &msg)
1731 {
1732     if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) {
1733         msg->removeHeader("X-Face");
1734     } else {
1735         QString xface = ident.xface();
1736         if (!xface.isEmpty()) {
1737             int numNL = (xface.length() - 1) / 70;
1738             for (int i = numNL; i > 0; --i) {
1739                 xface.insert(i * 70, QStringLiteral("\n\t"));
1740             }
1741             auto header = new KMime::Headers::Generic("X-Face");
1742             header->fromUnicodeString(xface, "utf-8");
1743             msg->setHeader(header);
1744         }
1745     }
1746 
1747     if (!ident.isFaceEnabled() || ident.face().isEmpty()) {
1748         msg->removeHeader("Face");
1749     } else {
1750         QString face = ident.face();
1751         if (!face.isEmpty()) {
1752             // The first line of data is 72 lines long to account for the
1753             // header name, the following lines are 76 lines long, like in
1754             // https://quimby.gnus.org/circus/face/
1755             if (face.length() > 72) {
1756                 int numNL = (face.length() - 73) / 76;
1757 
1758                 for (int i = numNL; i > 0; --i) {
1759                     face.insert(72 + i * 76, QStringLiteral("\n\t"));
1760                 }
1761 
1762                 face.insert(72, QStringLiteral("\n\t"));
1763             }
1764 
1765             auto header = new KMime::Headers::Generic("Face");
1766             header->fromUnicodeString(face, "utf-8");
1767             msg->setHeader(header);
1768         }
1769     }
1770 }
1771 
1772 void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg,
1773                                bool lastSignState,
1774                                bool lastEncryptState,
1775                                bool mayAutoSign,
1776                                bool allowDecryption,
1777                                bool isModified)
1778 {
1779     if (!newMsg) {
1780         qCDebug(KMAIL_LOG) << "newMsg == 0!";
1781         return;
1782     }
1783 
1784     if (lastSignState) {
1785         mLastSignActionState = true;
1786     }
1787 
1788     if (lastEncryptState) {
1789         mLastEncryptActionState = true;
1790     }
1791 
1792     const auto im = KMKernel::self()->identityManager();
1793 
1794     if (auto hrd = newMsg->headerByType("X-KMail-Identity")) {
1795         const QString identityStr = hrd->asUnicodeString();
1796         if (!identityStr.isEmpty()) {
1797             const auto ident = im->identityForUoid(identityStr.toUInt());
1798             if (ident.isNull()) {
1799                 if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) {
1800                     const QString identityStrName = hrd->asUnicodeString();
1801                     const auto id = im->modifyIdentityForName(identityStrName);
1802                     if (!id.isNull()) {
1803                         mId = id.uoid();
1804                     } else {
1805                         mId = 0;
1806                     }
1807                 } else {
1808                     mId = 0;
1809                 }
1810             } else {
1811                 mId = identityStr.toUInt();
1812             }
1813         }
1814     } else {
1815         if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) {
1816             const QString identityStrName = hrd->asUnicodeString();
1817             const auto id = im->modifyIdentityForName(identityStrName);
1818             if (!id.isNull()) {
1819                 mId = id.uoid();
1820             } else {
1821                 mId = 0;
1822             }
1823         } else {
1824             mId = 0;
1825         }
1826     }
1827 
1828     // don't overwrite the header values with identity specific values
1829     disconnect(mIdentityConnection);
1830 
1831     // load the mId into the gui, sticky or not, without emitting
1832     mComposerBase->identityCombo()->setCurrentIdentity(mId);
1833     mIdentityConnection = connect(mComposerBase->identityCombo(), &KIdentityManagementWidgets::IdentityCombo::identityChanged, this, [this](uint val) {
1834         slotIdentityChanged(val);
1835     });
1836 
1837     mMsg = newMsg;
1838 
1839     // manually load the identity's value into the fields; either the one from the
1840     // message, where appropriate, or the one from the sticky identity. What's in
1841     // mId might have changed meanwhile, thus the save value
1842     if (mId == 0) {
1843         mId = mComposerBase->identityCombo()->currentIdentity();
1844     }
1845     slotIdentityChanged(mId, true /*initalChange*/);
1846     // Fixing the identities with auto signing activated
1847     mLastSignActionState = sign();
1848 
1849     // Add initial data.
1850     MessageComposer::PluginEditorConverterInitialData data;
1851     data.setMewMsg(mMsg);
1852     data.setNewMessage(mContext == TemplateContext::New);
1853     mPluginEditorConvertTextManagerInterface->setInitialData(data);
1854 
1855     if (auto msgFrom = mMsg->from(false)) {
1856         mEdtFrom->setText(msgFrom->asUnicodeString());
1857     }
1858     if (auto msgSubject = mMsg->subject(false)) {
1859         mEdtSubject->setPlainText(msgSubject->asUnicodeString());
1860     }
1861 
1862     // Restore the quote prefix. We can't just use the global quote prefix here,
1863     // since the prefix is different for each message, it might for example depend
1864     // on the original sender in a reply.
1865     if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) {
1866         mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString());
1867     }
1868 
1869     // check for the presence of a DNT header, indicating that MDN's were requested
1870     if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) {
1871         const QString mdnAddr = hdr->asUnicodeString();
1872         mRequestMDNAction->setChecked((!mdnAddr.isEmpty() && im->thatIsMe(mdnAddr)) || KMailSettings::self()->requestMDN());
1873     }
1874     if (auto hdr = newMsg->headerByType("Return-Receipt-To")) {
1875         const QString returnReceiptToAddr = hdr->asUnicodeString();
1876         mRequestDeliveryConfirmation->setChecked((!returnReceiptToAddr.isEmpty() && im->thatIsMe(returnReceiptToAddr))
1877                                                  /*TODO || KMailSettings::self()->requestMDN()*/);
1878     }
1879     // check for presence of a priority header, indicating urgent mail:
1880     if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) {
1881         const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString();
1882         const QString priority = newMsg->headerByType("Priority")->asUnicodeString();
1883         if (xpriority == QLatin1StringView("2 (High)") && priority == QLatin1StringView("urgent")) {
1884             mUrgentAction->setChecked(true);
1885         }
1886     }
1887 
1888     const auto &ident = identity();
1889 
1890     addFaceHeaders(ident, mMsg);
1891 
1892     // if these headers are present, the state of the message should be overruled
1893     MessageComposer::DraftSignatureState signState(mMsg);
1894     if (signState.isDefined()) {
1895         mLastSignActionState = signState.signState();
1896     }
1897     MessageComposer::DraftEncryptionState encState(mMsg);
1898     if (encState.isDefined()) {
1899         mLastEncryptActionState = encState.encryptionState();
1900     }
1901     MessageComposer::DraftCryptoMessageFormatState formatState(mMsg);
1902     if (formatState.isDefined()) {
1903         mCryptoModuleAction->setCurrentItem(format2cb(formatState.cryptoMessageFormatState()));
1904     }
1905 
1906     mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1907     mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1908 
1909     if (QGpgME::openpgp() || QGpgME::smime()) {
1910         const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty();
1911         const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty();
1912 
1913         setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState);
1914     } else {
1915         mEncryptionState.setPossibleEncrypt(false);
1916     }
1917     updateSignatureAndEncryptionStateIndicators();
1918 
1919     // We need to set encryption/signing first before adding content.
1920     // This is important if we are in auto detect the encrypt mode.
1921     mComposerBase->setMessage(mMsg, allowDecryption);
1922 
1923     QString kmailFcc;
1924     if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) {
1925         kmailFcc = hdr->asUnicodeString();
1926     }
1927     if (kmailFcc.isEmpty()) {
1928         setFcc(ident.fcc());
1929     } else {
1930         setFcc(kmailFcc);
1931     }
1932     if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) {
1933         const QString dictionary = hdr->asUnicodeString();
1934         if (!dictionary.isEmpty()) {
1935             if (!mComposerBase->dictionary()->assignByDictionnary(dictionary)) {
1936                 mIncorrectIdentityFolderWarning->dictionaryInvalid();
1937             }
1938         }
1939     } else {
1940         mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
1941     }
1942 
1943     auto msgContent = new KMime::Content;
1944     msgContent->setContent(mMsg->encodedContent());
1945     msgContent->parse();
1946     MimeTreeParser::SimpleObjectTreeSource emptySource;
1947     MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
1948     emptySource.setDecryptMessage(allowDecryption);
1949     otp.parseObjectTree(msgContent);
1950 
1951     delete msgContent;
1952 
1953     if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1StringView("auto")) && mayAutoSign) {
1954         //
1955         // Espen 2000-05-16
1956         // Delay the signature appending. It may start a fileseletor.
1957         // Not user friendly if this modal fileseletor opens before the
1958         // composer.
1959         //
1960         if (MessageComposer::MessageComposerSettings::self()->prependSignature()) {
1961             QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1962         } else {
1963             QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1964         }
1965     } else {
1966         mComposerBase->editor()->externalComposer()->startExternalEditor();
1967     }
1968 
1969     setModified(isModified);
1970 
1971     // honor "keep reply in this folder" setting even when the identity is changed later on
1972     mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc);
1973     QTimer::singleShot(
1974         0,
1975         this,
1976         &KMComposerWin::forceAutoSaveMessage); // Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in.
1977 }
1978 
1979 void KMComposerWin::setAutoSaveFileName(const QString &fileName)
1980 {
1981     mComposerBase->setAutoSaveFileName(fileName);
1982 }
1983 
1984 void KMComposerWin::setSigningAndEncryptionDisabled(bool v)
1985 {
1986     mSigningAndEncryptionExplicitlyDisabled = v;
1987 }
1988 
1989 void KMComposerWin::setFolder(const Akonadi::Collection &aFolder)
1990 {
1991     mFolder = aFolder;
1992 }
1993 
1994 void KMComposerWin::setFcc(const QString &idString)
1995 {
1996     // check if the sent-mail folder still exists
1997     Akonadi::Collection col;
1998     if (idString.isEmpty()) {
1999         col = CommonKernel->sentCollectionFolder();
2000     } else {
2001         col = Akonadi::Collection(idString.toLongLong());
2002     }
2003     if (col.isValid()) {
2004         mComposerBase->setFcc(col);
2005         mFccFolder->setCollection(col);
2006         mIncorrectIdentityFolderWarning->clearFccInvalid();
2007     } else {
2008         mIncorrectIdentityFolderWarning->fccIsInvalid();
2009         qCWarning(KMAIL_LOG) << "setFcc: collection invalid " << idString;
2010     }
2011 }
2012 
2013 bool KMComposerWin::isComposerModified() const
2014 {
2015     return mComposerBase->editor()->document()->isModified() || mEdtFrom->isModified() || mComposerBase->recipientsEditor()->isModified()
2016         || mEdtSubject->document()->isModified();
2017 }
2018 
2019 bool KMComposerWin::isModified() const
2020 {
2021     return mWasModified || isComposerModified();
2022 }
2023 
2024 void KMComposerWin::setModified(bool modified)
2025 {
2026     mWasModified = modified;
2027     changeModifiedState(modified);
2028 }
2029 
2030 void KMComposerWin::changeModifiedState(bool modified)
2031 {
2032     mComposerBase->editor()->document()->setModified(modified);
2033     if (!modified) {
2034         mEdtFrom->setModified(false);
2035         mComposerBase->recipientsEditor()->clearModified();
2036         mEdtSubject->document()->setModified(false);
2037     }
2038 }
2039 
2040 bool KMComposerWin::queryClose()
2041 {
2042     if (!mComposerBase->editor()->checkExternalEditorFinished()) {
2043         return false;
2044     }
2045     if (kmkernel->shuttingDown() || qApp->isSavingSession()) {
2046         writeConfig();
2047         return true;
2048     }
2049 
2050     if (isModified()) {
2051         const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder));
2052         const QString savebut = (istemplate ? i18n("Re&save as Template") : i18n("&Save as Draft"));
2053         const QString savetext = (istemplate ? i18n("Resave this message in the Templates folder. "
2054                                                     "It can then be used at a later time.")
2055                                              : i18n("Save this message in the Drafts folder. "
2056                                                     "It can then be edited and sent at a later time."));
2057 
2058         const int rc = KMessageBox::warningTwoActionsCancel(this,
2059                                                             i18n("Do you want to save the message for later or discard it?"),
2060                                                             i18nc("@title:window", "Close Composer"),
2061                                                             KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext),
2062                                                             KStandardGuiItem::discard(),
2063                                                             KStandardGuiItem::cancel());
2064         if (rc == KMessageBox::Cancel) {
2065             return false;
2066         } else if (rc == KMessageBox::ButtonCode::PrimaryAction) {
2067             // doSend will close the window. Just return false from this method
2068             if (istemplate) {
2069                 slotSaveTemplate();
2070             } else {
2071                 slotSaveDraft();
2072             }
2073             return false;
2074         }
2075         // else fall through: return true
2076     }
2077     mComposerBase->cleanupAutoSave();
2078 
2079     if (!mMiscComposers.isEmpty()) {
2080         qCWarning(KMAIL_LOG) << "Tried to close while composer was active";
2081         return false;
2082     }
2083     writeConfig();
2084     return true;
2085 }
2086 
2087 MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment()
2088 {
2089     const bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning();
2090 
2091     if (!checkForForgottenAttachments) {
2092         return MessageComposer::ComposerViewBase::NoMissingAttachmentFound;
2093     }
2094 
2095     mComposerBase->setSubject(subject()); // be sure the composer knows the subject
2096     const MessageComposer::ComposerViewBase::MissingAttachment missingAttachments =
2097         mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords());
2098 
2099     return missingAttachments;
2100 }
2101 
2102 void KMComposerWin::forceAutoSaveMessage()
2103 {
2104     autoSaveMessage(true);
2105 }
2106 
2107 void KMComposerWin::autoSaveMessage(bool force)
2108 {
2109     if (isComposerModified() || force) {
2110         applyComposerSetting(mComposerBase);
2111         mComposerBase->saveMailSettings();
2112         mComposerBase->autoSaveMessage();
2113         if (!force) {
2114             mWasModified = true;
2115             changeModifiedState(false);
2116         }
2117     } else {
2118         mComposerBase->updateAutoSave();
2119     }
2120 }
2121 
2122 void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type)
2123 {
2124     setEnabled(true);
2125     if (!msg.isEmpty()) {
2126         KMessageBox::error(mMainWidget,
2127                            msg,
2128                            (type == MessageComposer::ComposerViewBase::AutoSave) ? i18nc("@title:window", "Autosave Message Failed")
2129                                                                                  : i18nc("@title:window", "Sending Message Failed"));
2130     }
2131 }
2132 
2133 void KMComposerWin::slotSendSuccessful(Akonadi::Item::Id id)
2134 {
2135     if (id != -1) {
2136         UndoSendManager::UndoSendManagerInfo info;
2137         info.subject = MessageCore::StringUtil::quoteHtmlChars(subject());
2138         info.index = id;
2139         info.delay = KMailSettings::self()->undoSendDelay();
2140         info.to = MessageCore::StringUtil::quoteHtmlChars(mComposerBase->to());
2141 
2142         UndoSendManager::self()->addItem(info);
2143     }
2144     setModified(false);
2145     mComposerBase->cleanupAutoSave();
2146     mFolder = Akonadi::Collection(); // see dtor
2147     close();
2148 }
2149 
2150 const KIdentityManagementCore::Identity &KMComposerWin::identity() const
2151 {
2152     return KMKernel::self()->identityManager()->identityForUoidOrDefault(currentIdentity());
2153 }
2154 
2155 bool KMComposerWin::pgpAutoEncrypt() const
2156 {
2157     const auto ident = identity();
2158     if (ident.encryptionOverride()) {
2159         return ident.pgpAutoEncrypt();
2160     } else {
2161         return MessageComposer::MessageComposerSettings::self()->cryptoAutoEncrypt();
2162     }
2163 }
2164 
2165 bool KMComposerWin::pgpAutoSign() const
2166 {
2167     const auto ident = identity();
2168     if (ident.encryptionOverride()) {
2169         return ident.pgpAutoSign();
2170     } else {
2171         return MessageComposer::MessageComposerSettings::self()->cryptoAutoSign();
2172     }
2173 }
2174 
2175 Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const
2176 {
2177     if (!mCryptoModuleAction) {
2178         return Kleo::AutoFormat;
2179     }
2180     return cb2format(mCryptoModuleAction->currentItem());
2181 }
2182 
2183 void KMComposerWin::addAttach(KMime::Content *msgPart)
2184 {
2185     mComposerBase->addAttachmentPart(msgPart);
2186     setModified(true);
2187 }
2188 
2189 void KMComposerWin::slotAddressBook()
2190 {
2191     auto job = new KIO::CommandLauncherJob(QStringLiteral("kaddressbook"), {}, this);
2192     job->setDesktopName(QStringLiteral("org.kde.kaddressbook"));
2193     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
2194     job->start();
2195 }
2196 
2197 void KMComposerWin::slotInsertFile()
2198 {
2199     const QUrl u = insertFile();
2200     if (u.isEmpty()) {
2201         return;
2202     }
2203 
2204     mRecentAction->addUrl(u);
2205     // Prevent race condition updating list when multiple composers are open
2206     {
2207         QUrlQuery query(u);
2208         const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset")));
2209         QStringList urls = KMailSettings::self()->recentUrls();
2210         QStringList encodings = KMailSettings::self()->recentEncodings();
2211         // Prevent config file from growing without bound
2212         // Would be nicer to get this constant from KRecentFilesAction
2213         const int mMaxRecentFiles = 30;
2214         while (urls.count() > mMaxRecentFiles) {
2215             urls.removeLast();
2216         }
2217         while (encodings.count() > mMaxRecentFiles) {
2218             encodings.removeLast();
2219         }
2220         // sanity check
2221         if (urls.count() != encodings.count()) {
2222             urls.clear();
2223             encodings.clear();
2224         }
2225         urls.prepend(u.toDisplayString());
2226         encodings.prepend(encoding);
2227         KMailSettings::self()->setRecentUrls(urls);
2228         KMailSettings::self()->setRecentEncodings(encodings);
2229         KMailSettings::self()->save();
2230     }
2231     slotInsertRecentFile(u);
2232 }
2233 
2234 void KMComposerWin::slotRecentListFileClear()
2235 {
2236     KSharedConfig::Ptr config = KMKernel::self()->config();
2237     KConfigGroup group(config, QStringLiteral("Composer"));
2238     group.deleteEntry("recent-urls");
2239     group.deleteEntry("recent-encoding");
2240     KMailSettings::self()->save();
2241 }
2242 
2243 void KMComposerWin::slotInsertRecentFile(const QUrl &u)
2244 {
2245     if (u.fileName().isEmpty()) {
2246         return;
2247     }
2248 
2249     // Get the encoding previously used when inserting this file
2250     QString encoding;
2251     const QStringList urls = KMailSettings::self()->recentUrls();
2252     const QStringList encodings = KMailSettings::self()->recentEncodings();
2253     const int index = urls.indexOf(u.toDisplayString());
2254     if (index != -1) {
2255         encoding = encodings[index];
2256     } else {
2257         qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; // see InsertTextFileJob
2258         return;
2259     }
2260     auto job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u);
2261     job->setEncoding(encoding);
2262     connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile);
2263     job->start();
2264 }
2265 
2266 bool KMComposerWin::showErrorMessage(KJob *job)
2267 {
2268     if (job->error()) {
2269         if (auto uiDelegate = static_cast<KIO::Job *>(job)->uiDelegate()) {
2270             uiDelegate->showErrorMessage();
2271         } else {
2272             qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString();
2273         }
2274         return true;
2275     }
2276     return false;
2277 }
2278 
2279 void KMComposerWin::slotInsertTextFile(KJob *job)
2280 {
2281     showErrorMessage(job);
2282 }
2283 
2284 void KMComposerWin::slotCryptoModuleSelected()
2285 {
2286     slotSelectCryptoModule(false);
2287 }
2288 
2289 void KMComposerWin::slotSelectCryptoModule(bool init)
2290 {
2291     if (!init) {
2292         setModified(true);
2293     }
2294 
2295     mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments());
2296     mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments());
2297 }
2298 
2299 void KMComposerWin::slotUpdateFont()
2300 {
2301     if (!mFixedFontAction) {
2302         return;
2303     }
2304     const QFont plaintextFont = mFixedFontAction->isChecked() ? mFixedFont : mBodyFont;
2305     mComposerBase->editor()->composerControler()->setFontForWholeText(plaintextFont);
2306     mComposerBase->editor()->setDefaultFontSize(plaintextFont.pointSize());
2307 }
2308 
2309 QUrl KMComposerWin::insertFile()
2310 {
2311     QString recentDirClass;
2312     QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///InsertFile")), recentDirClass);
2313 
2314     const KEncodingFileDialog::Result result =
2315         KEncodingFileDialog::getOpenUrlAndEncoding(QString(), startUrl, QString(), this, i18nc("@title:window", "Insert File"));
2316     QUrl url;
2317     if (!result.URLs.isEmpty()) {
2318         url = result.URLs.constFirst();
2319         if (url.isValid()) {
2320             MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding));
2321             if (!recentDirClass.isEmpty()) {
2322                 KRecentDirs::add(recentDirClass, url.path());
2323             }
2324         }
2325     }
2326     return url;
2327 }
2328 
2329 QString KMComposerWin::smartQuote(const QString &msg)
2330 {
2331     return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
2332 }
2333 
2334 void KMComposerWin::insertUrls(const QMimeData *source, const QList<QUrl> &urlList)
2335 {
2336     QStringList urlAdded;
2337     for (const QUrl &url : urlList) {
2338         QString urlStr;
2339         if (url.scheme() == QLatin1StringView("mailto")) {
2340             urlStr = KEmailAddress::decodeMailtoUrl(url);
2341         } else {
2342             urlStr = url.toDisplayString();
2343             // Workaround #346370
2344             if (urlStr.isEmpty()) {
2345                 urlStr = source->text();
2346             }
2347         }
2348         if (!urlAdded.contains(urlStr)) {
2349             mComposerBase->editor()->composerControler()->insertLink(urlStr);
2350             urlAdded.append(urlStr);
2351         }
2352     }
2353 }
2354 
2355 bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment)
2356 {
2357     // If this is a PNG image, either add it as an attachment or as an inline image
2358     if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) {
2359         const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html")));
2360         mComposerBase->editor()->insertHtml(html);
2361         return true;
2362     } else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText()
2363                && !forceAttachment) {
2364         mComposerBase->editor()->insertPlainText(source->text());
2365         return true;
2366     } else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) {
2367         // Get the image data before showing the dialog, since that processes events which can delete
2368         // the QMimeData object behind our back
2369         const QByteArray imageData = source->data(QStringLiteral("image/png"));
2370         if (imageData.isEmpty()) {
2371             return true;
2372         }
2373         if (!forceAttachment) {
2374             if (mComposerBase->editor()->textMode()
2375                 == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
2376                 auto image = qvariant_cast<QImage>(source->imageData());
2377                 QFileInfo fi(source->text());
2378 
2379                 QMenu menu(this);
2380                 const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image"));
2381                 menu.addAction(i18n("Add as &Attachment"));
2382                 const QAction *selectedAction = menu.exec(QCursor::pos());
2383                 if (selectedAction == addAsInlineImageAction) {
2384                     // Let the textedit from kdepimlibs handle inline images
2385                     mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi);
2386                     return true;
2387                 } else if (!selectedAction) {
2388                     return true;
2389                 }
2390                 // else fall through
2391             }
2392         }
2393         // Ok, when we reached this point, the user wants to add the image as an attachment.
2394         // Ask for the filename first.
2395         bool ok;
2396         QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
2397         if (!ok) {
2398             return true;
2399         }
2400         attName = attName.trimmed();
2401         if (attName.isEmpty()) {
2402             KMessageBox::error(this, i18n("Attachment name can't be empty"), i18nc("@title:window", "Invalid Attachment Name"));
2403 
2404             return true;
2405         }
2406         addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png");
2407         return true;
2408     } else {
2409         auto job = new DndFromArkJob(this);
2410         job->setComposerWin(this);
2411         if (job->extract(source)) {
2412             return true;
2413         }
2414     }
2415 
2416     // If this is a URL list, add those files as attachments or text
2417     // but do not offer this if we are pasting plain text containing an url, e.g. from a browser
2418     const QList<QUrl> urlList = source->urls();
2419     if (!urlList.isEmpty()) {
2420         // Search if it's message items.
2421         Akonadi::Item::List items;
2422         Akonadi::Collection::List collections;
2423         bool allLocalURLs = true;
2424 
2425         for (const QUrl &url : urlList) {
2426             if (!url.isLocalFile()) {
2427                 allLocalURLs = false;
2428             }
2429             const Akonadi::Item item = Akonadi::Item::fromUrl(url);
2430             if (item.isValid()) {
2431                 items << item;
2432             } else {
2433                 const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url);
2434                 if (collection.isValid()) {
2435                     collections << collection;
2436                 }
2437             }
2438         }
2439 
2440         if (items.isEmpty() && collections.isEmpty()) {
2441             if (allLocalURLs || forceAttachment) {
2442                 QList<AttachmentInfo> infoList;
2443                 infoList.reserve(urlList.count());
2444                 for (const QUrl &url : urlList) {
2445                     AttachmentInfo info;
2446                     info.url = url;
2447                     infoList.append(std::move(info));
2448                 }
2449                 addAttachment(infoList, false);
2450             } else {
2451                 QMenu p;
2452                 const int sizeUrl(urlList.size());
2453                 const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl));
2454                 const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl));
2455                 const QAction *selectedAction = p.exec(QCursor::pos());
2456 
2457                 if (selectedAction == addAsTextAction) {
2458                     insertUrls(source, urlList);
2459                 } else if (selectedAction == addAsAttachmentAction) {
2460                     QList<AttachmentInfo> infoList;
2461                     for (const QUrl &url : urlList) {
2462                         if (url.isValid()) {
2463                             AttachmentInfo info;
2464                             info.url = url;
2465                             infoList.append(std::move(info));
2466                         }
2467                     }
2468                     addAttachment(infoList, false);
2469                 }
2470             }
2471             return true;
2472         } else {
2473             if (!items.isEmpty()) {
2474                 auto itemFetchJob = new Akonadi::ItemFetchJob(items, this);
2475                 itemFetchJob->fetchScope().fetchFullPayload(true);
2476                 itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
2477                 connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob);
2478             }
2479             if (!collections.isEmpty()) {
2480                 qCDebug(KMAIL_LOG) << "Collection dnd not supported";
2481             }
2482             return true;
2483         }
2484     }
2485     return false;
2486 }
2487 
2488 void KMComposerWin::slotPasteAsAttachment()
2489 {
2490     const QMimeData *mimeData = QApplication::clipboard()->mimeData();
2491     if (!mimeData) {
2492         return;
2493     }
2494     if (insertFromMimeData(mimeData, true)) {
2495         return;
2496     }
2497     if (mimeData->hasText()) {
2498         bool ok;
2499         const QString attName =
2500             QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
2501         if (ok) {
2502             mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
2503         }
2504         return;
2505     }
2506 }
2507 
2508 void KMComposerWin::slotFetchJob(KJob *job)
2509 {
2510     if (showErrorMessage(job)) {
2511         return;
2512     }
2513     auto fjob = qobject_cast<Akonadi::ItemFetchJob *>(job);
2514     if (!fjob) {
2515         return;
2516     }
2517     const Akonadi::Item::List items = fjob->items();
2518 
2519     if (items.isEmpty()) {
2520         return;
2521     }
2522 
2523     if (items.constFirst().mimeType() == KMime::Message::mimeType()) {
2524         uint identity = 0;
2525         if (items.at(0).isValid()) {
2526             const Akonadi::Collection parentCollection = items.at(0).parentCollection();
2527             if (parentCollection.isValid()) {
2528                 const QString resourceName = parentCollection.resource();
2529                 if (!resourceName.isEmpty()) {
2530                     QSharedPointer<MailCommon::FolderSettings> fd(MailCommon::FolderSettings::forCollection(parentCollection, false));
2531                     if (!fd.isNull()) {
2532                         identity = fd->identity();
2533                     }
2534                 }
2535             }
2536         }
2537         KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this);
2538         command->start();
2539     } else {
2540         for (const Akonadi::Item &item : items) {
2541             QString attachmentName = QStringLiteral("attachment");
2542             if (item.hasPayload<KContacts::Addressee>()) {
2543                 const auto contact = item.payload<KContacts::Addressee>();
2544                 attachmentName = contact.realName() + QLatin1StringView(".vcf");
2545                 // Workaround about broken kaddressbook fields.
2546                 QByteArray data = item.payloadData();
2547                 KContacts::adaptIMAttributes(data);
2548                 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard");
2549             } else if (item.hasPayload<KContacts::ContactGroup>()) {
2550                 const auto group = item.payload<KContacts::ContactGroup>();
2551                 attachmentName = group.name() + QLatin1StringView(".vcf");
2552                 auto expandJob = new Akonadi::ContactGroupExpandJob(group, this);
2553                 expandJob->setProperty("groupName", attachmentName);
2554                 connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult);
2555                 expandJob->start();
2556             } else {
2557                 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1());
2558             }
2559         }
2560     }
2561 }
2562 
2563 void KMComposerWin::slotExpandGroupResult(KJob *job)
2564 {
2565     auto expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job);
2566     Q_ASSERT(expandJob);
2567 
2568     const QString attachmentName = expandJob->property("groupName").toString();
2569     KContacts::VCardConverter converter;
2570     const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0);
2571     if (!groupData.isEmpty()) {
2572         addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard");
2573     }
2574 }
2575 
2576 void KMComposerWin::slotClose()
2577 {
2578     close();
2579 }
2580 
2581 void KMComposerWin::slotNewComposer()
2582 {
2583     auto job = new KMComposerCreateNewComposerJob;
2584     job->setCollectionForNewMessage(mCollectionForNewMessage);
2585 
2586     job->setCurrentIdentity(currentIdentity());
2587     job->start();
2588 }
2589 
2590 void KMComposerWin::slotUpdateWindowTitle()
2591 {
2592     QString s(mEdtSubject->toPlainText());
2593     mComposerBase->setSubject(s);
2594     // Remove characters that show badly in most window decorations:
2595     // newlines tend to become boxes.
2596     if (s.isEmpty()) {
2597         setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')'));
2598     } else {
2599         setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' ')));
2600     }
2601 }
2602 
2603 void KMComposerWin::slotSignToggled(bool on)
2604 {
2605     setSigning(on, true);
2606     updateSignatureAndEncryptionStateIndicators();
2607 }
2608 
2609 void KMComposerWin::setSigning(bool sign, bool setByUser)
2610 {
2611     const bool wasModified = isModified();
2612     if (setByUser) {
2613         setModified(true);
2614     }
2615     if (!mSignAction->isEnabled()) {
2616         sign = false;
2617     }
2618 
2619     // check if the user defined a signing key for the current identity
2620     if (sign && !mLastIdentityHasSigningKey) {
2621         if (setByUser) {
2622             KMessageBox::error(this,
2623                                i18n("<qt><p>In order to be able to sign "
2624                                     "this message you first have to "
2625                                     "define the (OpenPGP or S/MIME) signing key "
2626                                     "to use.</p>"
2627                                     "<p>Please select the key to use "
2628                                     "in the identity configuration.</p>"
2629                                     "</qt>"),
2630                                i18nc("@title:window", "Undefined Signing Key"));
2631             setModified(wasModified);
2632         }
2633         sign = false;
2634     }
2635 
2636     // make sure the mSignAction is in the right state
2637     mSignAction->setChecked(sign);
2638 
2639     if (!setByUser) {
2640         updateSignatureAndEncryptionStateIndicators();
2641     }
2642     // mark the attachments for (no) signing
2643     if (canSignEncryptAttachments()) {
2644         mComposerBase->attachmentModel()->setSignSelected(sign);
2645     }
2646 }
2647 
2648 void KMComposerWin::slotWordWrapToggled(bool on)
2649 {
2650     if (on) {
2651         mComposerBase->editor()->enableWordWrap(validateLineWrapWidth());
2652     } else {
2653         disableWordWrap();
2654     }
2655 }
2656 
2657 int KMComposerWin::validateLineWrapWidth() const
2658 {
2659     int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
2660     if ((lineWrap == 0) || (lineWrap > 78)) {
2661         lineWrap = 78;
2662     } else if (lineWrap < 30) {
2663         lineWrap = 30;
2664     }
2665     return lineWrap;
2666 }
2667 
2668 void KMComposerWin::disableWordWrap()
2669 {
2670     mComposerBase->editor()->disableWordWrap();
2671 }
2672 
2673 void KMComposerWin::forceDisableHtml()
2674 {
2675     mForceDisableHtml = true;
2676     disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2677     mMarkupAction->setEnabled(false);
2678     // FIXME: Remove the toggle toolbar action somehow
2679 }
2680 
2681 bool KMComposerWin::isComposing() const
2682 {
2683     return mComposerBase && mComposerBase->isComposing();
2684 }
2685 
2686 void KMComposerWin::disableForgottenAttachmentsCheck()
2687 {
2688     mCheckForForgottenAttachments = false;
2689 }
2690 
2691 void KMComposerWin::slotPrint()
2692 {
2693     printComposer(false);
2694 }
2695 
2696 void KMComposerWin::slotPrintPreview()
2697 {
2698     printComposer(true);
2699 }
2700 
2701 void KMComposerWin::printComposer(bool preview)
2702 {
2703     auto composer = new MessageComposer::Composer();
2704     mComposerBase->fillComposer(composer);
2705     mMiscComposers.append(composer);
2706     composer->setProperty("preview", preview);
2707     connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult);
2708     composer->start();
2709 }
2710 
2711 void KMComposerWin::slotPrintComposeResult(KJob *job)
2712 {
2713     const bool preview = job->property("preview").toBool();
2714     printComposeResult(job, preview);
2715 }
2716 
2717 void KMComposerWin::printComposeResult(KJob *job, bool preview)
2718 {
2719     Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
2720     auto composer = qobject_cast<MessageComposer::Composer *>(job);
2721     Q_ASSERT(mMiscComposers.contains(composer));
2722     mMiscComposers.removeAll(composer);
2723 
2724     if (composer->error() == MessageComposer::Composer::NoError) {
2725         Q_ASSERT(composer->resultMessages().size() == 1);
2726         Akonadi::Item printItem;
2727         printItem.setPayload<KMime::Message::Ptr>(composer->resultMessages().constFirst());
2728         Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().constFirst()), printItem);
2729         const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich;
2730         const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text;
2731         KMPrintCommandInfo commandInfo;
2732         commandInfo.mMsg = printItem;
2733         commandInfo.mFormat = format;
2734         commandInfo.mHtmlLoadExtOverride = isHtml;
2735         commandInfo.mPrintPreview = preview;
2736         auto command = new KMPrintCommand(this, commandInfo);
2737         command->start();
2738     } else {
2739         showErrorMessage(job);
2740     }
2741 }
2742 
2743 void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool willSendItWithoutReediting)
2744 {
2745     if (saveIn == MessageComposer::MessageSender::SaveInNone) {
2746         const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment();
2747         if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment)
2748             || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) {
2749             return;
2750         }
2751     }
2752 
2753     // TODO generate new message from plugins.
2754     MessageComposer::PluginEditorConverterBeforeConvertingData data;
2755     data.setNewMessage(mContext == TemplateContext::New);
2756     mPluginEditorConvertTextManagerInterface->setDataBeforeConvertingText(data);
2757 
2758     // TODO converttext if necessary
2759 
2760     // TODO integrate with MDA online status
2761     if (method == MessageComposer::MessageSender::SendImmediate) {
2762         qDebug() << " VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV";
2763         if (!MessageComposer::Util::sendMailDispatcherIsOnline()) {
2764             qDebug() << " CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
2765             method = MessageComposer::MessageSender::SendLater;
2766         }
2767         if (KMailSettings::self()->enabledUndoSend()) {
2768             mComposerBase->setSendLaterInfo(nullptr);
2769             const bool wasRegistered = sendLaterRegistered();
2770             if (wasRegistered) {
2771                 auto info = new MessageComposer::SendLaterInfo;
2772                 info->setRecurrence(false);
2773                 info->setSubject(subject());
2774                 info->setDateTime(QDateTime::currentDateTime().addSecs(KMailSettings::self()->undoSendDelay()));
2775                 mComposerBase->setSendLaterInfo(info);
2776             }
2777             method = MessageComposer::MessageSender::SendLater;
2778             willSendItWithoutReediting = true;
2779             saveIn = MessageComposer::MessageSender::SaveInOutbox;
2780         }
2781     }
2782 
2783     if (saveIn == MessageComposer::MessageSender::SaveInNone || willSendItWithoutReediting) { // don't save as draft or template, send immediately
2784         if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2785             if (!(mShowHeaders & HDR_FROM)) {
2786                 mShowHeaders |= HDR_FROM;
2787                 rethinkFields(false);
2788             }
2789             mEdtFrom->setFocus();
2790             KMessageBox::error(this,
2791                                i18n("You must enter your email address in the "
2792                                     "From: field. You should also set your email "
2793                                     "address for all identities, so that you do "
2794                                     "not have to enter it for each message."));
2795             return;
2796         }
2797         if (mComposerBase->to().isEmpty()) {
2798             if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) {
2799                 KMessageBox::information(this,
2800                                          i18n("You must specify at least one receiver, "
2801                                               "either in the To: field or as CC or as BCC."));
2802 
2803                 return;
2804             } else {
2805                 const int rc = KMessageBox::questionTwoActions(this,
2806                                                                i18n("To: field is empty. "
2807                                                                     "Send message anyway?"),
2808                                                                i18nc("@title:window", "No To: specified"),
2809                                                                KGuiItem(i18n("S&end as Is"), QLatin1StringView("mail-send")),
2810                                                                KGuiItem(i18n("&Specify the To field"), QLatin1StringView("edit-rename")));
2811                 if (rc == KMessageBox::ButtonCode::SecondaryAction) {
2812                     return;
2813                 }
2814             }
2815         }
2816 
2817         if (subject().isEmpty()) {
2818             mEdtSubject->setFocus();
2819             const int rc = KMessageBox::questionTwoActions(this,
2820                                                            i18n("You did not specify a subject. "
2821                                                                 "Send message anyway?"),
2822                                                            i18nc("@title:window", "No Subject Specified"),
2823                                                            KGuiItem(i18n("S&end as Is"), QStringLiteral("mail-send")),
2824                                                            KGuiItem(i18n("&Specify the Subject"), QStringLiteral("edit-rename")));
2825             if (rc == KMessageBox::ButtonCode::SecondaryAction) {
2826                 return;
2827             }
2828         }
2829 
2830         MessageComposer::PluginEditorCheckBeforeSendParams params;
2831         params.setSubject(subject());
2832         params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
2833         params.setIdentity(currentIdentity());
2834         params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0);
2835         params.setTransportId(mComposerBase->transportComboBox()->currentTransportId());
2836         const auto ident = identity();
2837         QString defaultDomainName;
2838         if (!ident.isNull()) {
2839             defaultDomainName = ident.defaultDomainName();
2840         }
2841         const QString composerBaseBccTrimmed = mComposerBase->bcc().trimmed();
2842         const QString composerBaseToTrimmed = mComposerBase->to().trimmed();
2843         const QString composerBaseCcTrimmed = mComposerBase->cc().trimmed();
2844         params.setBccAddresses(composerBaseBccTrimmed);
2845         params.setToAddresses(composerBaseToTrimmed);
2846         params.setCcAddresses(composerBaseCcTrimmed);
2847         params.setDefaultDomain(defaultDomainName);
2848 
2849         if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) {
2850             return;
2851         }
2852         const QStringList recipients = {composerBaseToTrimmed, composerBaseCcTrimmed, composerBaseBccTrimmed};
2853 
2854         setEnabled(false);
2855 
2856         // Validate the To:, CC: and BCC fields
2857         auto job = new AddressValidationJob(recipients.join(QLatin1StringView(", ")), this, this);
2858         job->setDefaultDomain(defaultDomainName);
2859         job->setProperty("method", static_cast<int>(method));
2860         job->setProperty("saveIn", static_cast<int>(saveIn));
2861         connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend);
2862         job->start();
2863 
2864         // we'll call send from within slotDoDelaySend
2865     } else {
2866         if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptionState.encrypt() && KMailSettings::self()->alwaysEncryptDrafts()
2867             && mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) {
2868             KMessageBox::information(this,
2869                                      i18n("You must specify at least one receiver "
2870                                           "in order to be able to encrypt a draft."));
2871             return;
2872         }
2873         doDelayedSend(method, saveIn);
2874     }
2875 }
2876 
2877 void KMComposerWin::slotDoDelayedSend(KJob *job)
2878 {
2879     if (job->error()) {
2880         KMessageBox::error(this, job->errorText());
2881         setEnabled(true);
2882         return;
2883     }
2884 
2885     const AddressValidationJob *validateJob = qobject_cast<AddressValidationJob *>(job);
2886 
2887     // Abort sending if one of the recipient addresses is invalid ...
2888     if (!validateJob->isValid()) {
2889         setEnabled(true);
2890         return;
2891     }
2892 
2893     // ... otherwise continue as usual
2894     const MessageComposer::MessageSender::SendMethod method = static_cast<MessageComposer::MessageSender::SendMethod>(job->property("method").toInt());
2895     const MessageComposer::MessageSender::SaveIn saveIn = static_cast<MessageComposer::MessageSender::SaveIn>(job->property("saveIn").toInt());
2896 
2897     doDelayedSend(method, saveIn);
2898 }
2899 
2900 void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase)
2901 {
2902     mComposerBase->setFrom(from());
2903     mComposerBase->setSubject(subject());
2904     mComposerBase->setUrgent(mUrgentAction->isChecked());
2905     mComposerBase->setMDNRequested(mRequestMDNAction->isChecked());
2906     mComposerBase->setRequestDeleveryConfirmation(mRequestDeliveryConfirmation->isChecked());
2907 }
2908 
2909 void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn)
2910 {
2911     KCursorSaver saver(Qt::WaitCursor);
2912     applyComposerSetting(mComposerBase);
2913     if (mForceDisableHtml) {
2914         disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2915     }
2916     const bool encrypt = mEncryptionState.encrypt();
2917 
2918     mComposerBase->setCryptoOptions(
2919         sign(),
2920         encrypt,
2921         cryptoMessageFormat(),
2922         ((saveIn != MessageComposer::MessageSender::SaveInNone && !KMailSettings::self()->alwaysEncryptDrafts()) || mSigningAndEncryptionExplicitlyDisabled));
2923 
2924     const int num = KMailSettings::self()->customMessageHeadersCount();
2925     QMap<QByteArray, QString> customHeader;
2926     for (int ix = 0; ix < num; ++ix) {
2927         CustomMimeHeader customMimeHeader(QString::number(ix));
2928         customMimeHeader.load();
2929         customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue());
2930     }
2931 
2932     QMap<QByteArray, QString>::const_iterator extraCustomHeader = mExtraHeaders.constBegin();
2933     while (extraCustomHeader != mExtraHeaders.constEnd()) {
2934         customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value());
2935         ++extraCustomHeader;
2936     }
2937 
2938     mComposerBase->setCustomHeader(customHeader);
2939     mComposerBase->send(method, saveIn, false);
2940 }
2941 
2942 bool KMComposerWin::sendLaterRegistered() const
2943 {
2944     return MessageComposer::SendLaterUtil::sentLaterAgentWasRegistered() && MessageComposer::SendLaterUtil::sentLaterAgentEnabled();
2945 }
2946 
2947 void KMComposerWin::slotSendLater()
2948 {
2949     if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
2950         return;
2951     }
2952     if (!checkRecipientNumber()) {
2953         return;
2954     }
2955     mComposerBase->setSendLaterInfo(nullptr);
2956     if (mComposerBase->editor()->checkExternalEditorFinished()) {
2957         const bool wasRegistered = sendLaterRegistered();
2958         if (wasRegistered) {
2959             MessageComposer::SendLaterInfo *info = nullptr;
2960             QPointer<MessageComposer::SendLaterDialog> dlg = new MessageComposer::SendLaterDialog(info, this);
2961             if (dlg->exec()) {
2962                 info = dlg->info();
2963                 const MessageComposer::SendLaterDialog::SendLaterAction action = dlg->action();
2964                 delete dlg;
2965                 switch (action) {
2966                 case MessageComposer::SendLaterDialog::Unknown:
2967                     qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it.";
2968                     break;
2969                 case MessageComposer::SendLaterDialog::Canceled:
2970                     return;
2971                     break;
2972                 case MessageComposer::SendLaterDialog::PutInOutbox:
2973                     doSend(MessageComposer::MessageSender::SendLater);
2974                     break;
2975                 case MessageComposer::SendLaterDialog::SendDeliveryAtTime:
2976                     mComposerBase->setSendLaterInfo(info);
2977                     if (info->isRecurrence()) {
2978                         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates, true);
2979                     } else {
2980                         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts, true);
2981                     }
2982                     break;
2983                 }
2984             } else {
2985                 delete dlg;
2986             }
2987         } else {
2988             doSend(MessageComposer::MessageSender::SendLater);
2989         }
2990     }
2991 }
2992 
2993 void KMComposerWin::slotSaveDraft()
2994 {
2995     if (mComposerBase->editor()->checkExternalEditorFinished()) {
2996         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts);
2997     }
2998 }
2999 
3000 void KMComposerWin::slotSaveTemplate()
3001 {
3002     if (mComposerBase->editor()->checkExternalEditorFinished()) {
3003         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates);
3004     }
3005 }
3006 
3007 void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport)
3008 {
3009     if (transport) {
3010         mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3011         slotSendNow();
3012     }
3013 }
3014 
3015 void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport)
3016 {
3017     if (transport) {
3018         mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3019         slotSendLater();
3020     }
3021 }
3022 
3023 void KMComposerWin::sendNow(bool shortcutUsed)
3024 {
3025     if (!mComposerBase->editor()->checkExternalEditorFinished()) {
3026         return;
3027     }
3028     if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
3029         return;
3030     }
3031     if (!checkRecipientNumber()) {
3032         return;
3033     }
3034     mSendNowByShortcutUsed = shortcutUsed;
3035     if (KMailSettings::self()->checkSpellingBeforeSend()) {
3036         mComposerBase->editor()->forceSpellChecking();
3037     } else {
3038         slotCheckSendNow();
3039     }
3040 }
3041 
3042 void KMComposerWin::slotSendNowByShortcut()
3043 {
3044     sendNow(true);
3045 }
3046 
3047 void KMComposerWin::slotSendNow()
3048 {
3049     sendNow(false);
3050 }
3051 
3052 void KMComposerWin::confirmBeforeSend()
3053 {
3054     const int rc = KMessageBox::warningTwoActionsCancel(mMainWidget,
3055                                                         i18n("About to send email..."),
3056                                                         i18nc("@title:window", "Send Confirmation"),
3057                                                         KGuiItem(i18n("&Send Now"), QLatin1StringView("mail-send")),
3058                                                         KGuiItem(i18n("Send &Later"), QLatin1StringView("mail-queue")));
3059 
3060     if (rc == KMessageBox::ButtonCode::PrimaryAction) {
3061         doSend(MessageComposer::MessageSender::SendImmediate);
3062     } else if (rc == KMessageBox::ButtonCode::SecondaryAction) {
3063         doSend(MessageComposer::MessageSender::SendLater);
3064     }
3065 }
3066 
3067 void KMComposerWin::slotCheckSendNowStep2()
3068 {
3069     if (KMailSettings::self()->confirmBeforeSend()) {
3070         confirmBeforeSend();
3071     } else {
3072         if (mSendNowByShortcutUsed) {
3073             if (!KMailSettings::self()->checkSendDefaultActionShortcut()) {
3074                 ValidateSendMailShortcut validateShortcut(actionCollection(), this);
3075                 if (!validateShortcut.validate()) {
3076                     return;
3077                 }
3078             }
3079             if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) {
3080                 confirmBeforeSend();
3081                 return;
3082             }
3083         }
3084         doSend(MessageComposer::MessageSender::SendImmediate);
3085     }
3086 }
3087 
3088 void KMComposerWin::slotDelayedCheckSendNow()
3089 {
3090     QTimer::singleShot(0, this, &KMComposerWin::slotCheckSendNow);
3091 }
3092 
3093 void KMComposerWin::slotCheckSendNow()
3094 {
3095     QStringList lst{mComposerBase->to()};
3096     const QString ccStr = mComposerBase->cc();
3097     if (!ccStr.isEmpty()) {
3098         lst << KEmailAddress::splitAddressList(ccStr);
3099     }
3100     const QString bccStr = mComposerBase->bcc();
3101     if (!bccStr.isEmpty()) {
3102         lst << KEmailAddress::splitAddressList(bccStr);
3103     }
3104     if (lst.isEmpty()) {
3105         slotCheckSendNowStep2();
3106     } else {
3107         auto job = new PotentialPhishingEmailJob(this);
3108         KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("PotentialPhishing"));
3109         const QStringList whiteList = group.readEntry("whiteList", QStringList());
3110         job->setEmailWhiteList(whiteList);
3111         job->setPotentialPhishingEmails(lst);
3112         connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound);
3113         if (!job->start()) {
3114             qCWarning(KMAIL_LOG) << "PotentialPhishingEmailJob can't start";
3115         }
3116     }
3117 }
3118 
3119 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list)
3120 {
3121     if (list.isEmpty()) {
3122         slotCheckSendNowStep2();
3123     } else {
3124         mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list);
3125     }
3126 }
3127 
3128 bool KMComposerWin::checkRecipientNumber() const
3129 {
3130     const int thresHold = KMailSettings::self()->recipientThreshold();
3131     if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) {
3132         if (KMessageBox::questionTwoActions(mMainWidget,
3133                                             i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold),
3134                                             i18n("Too many recipients"),
3135                                             KGuiItem(i18n("&Send as Is")),
3136                                             KGuiItem(i18n("&Edit Recipients")))
3137             == KMessageBox::ButtonCode::SecondaryAction) {
3138             return false;
3139         }
3140     }
3141     return true;
3142 }
3143 
3144 void KMComposerWin::enableHtml()
3145 {
3146     if (mForceDisableHtml) {
3147         disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
3148         return;
3149     }
3150 
3151     mComposerBase->editor()->activateRichText();
3152     if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
3153         // Use singleshot, as we we might actually be called from a slot that wanted to disable the
3154         // toolbar (but the messagebox in disableHtml() prevented that and called us).
3155         // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
3156         // signal, so wait one event loop run for that.
3157         QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show);
3158     }
3159     if (!mMarkupAction->isChecked()) {
3160         mMarkupAction->setChecked(true);
3161     }
3162 
3163     mComposerBase->editor()->composerActions()->updateActionStates();
3164     mComposerBase->editor()->composerActions()->setActionsEnabled(true);
3165 }
3166 
3167 void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation)
3168 {
3169     bool forcePlainTextMarkup = false;
3170     if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed()
3171         && !mForceDisableHtml) {
3172         int choice = KMessageBox::warningTwoActionsCancel(this,
3173                                                           i18n("Turning HTML mode off "
3174                                                                "will cause the text to lose the formatting. Are you sure?"),
3175                                                           i18n("Lose the formatting?"),
3176                                                           KGuiItem(i18n("Lose Formatting")),
3177                                                           KGuiItem(i18n("Add Markup Plain Text")),
3178                                                           KStandardGuiItem::cancel(),
3179                                                           QStringLiteral("LoseFormattingWarning"));
3180 
3181         switch (choice) {
3182         case KMessageBox::Cancel:
3183             enableHtml();
3184             return;
3185         case KMessageBox::ButtonCode::SecondaryAction:
3186             forcePlainTextMarkup = true;
3187             break;
3188         case KMessageBox::ButtonCode::PrimaryAction:
3189             break;
3190         }
3191     }
3192 
3193     mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup);
3194     mComposerBase->editor()->switchToPlainText();
3195     mComposerBase->editor()->composerActions()->setActionsEnabled(false);
3196 
3197     slotUpdateFont();
3198     if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
3199         // See the comment in enableHtml() why we use a singleshot timer, similar situation here.
3200         QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide);
3201     }
3202     if (mMarkupAction->isChecked()) {
3203         mMarkupAction->setChecked(false);
3204     }
3205 }
3206 
3207 void KMComposerWin::slotToggleMarkup()
3208 {
3209     htmlToolBarVisibilityChanged(mMarkupAction->isChecked());
3210 }
3211 
3212 void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode)
3213 {
3214     if (mode == MessageComposer::RichTextComposerNg::Plain) {
3215         disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all?
3216     } else {
3217         enableHtml();
3218     }
3219     enableDisablePluginActions(mode == MessageComposer::RichTextComposerNg::Rich);
3220 }
3221 
3222 void KMComposerWin::enableDisablePluginActions(bool richText)
3223 {
3224     mPluginEditorConvertTextManagerInterface->enableDisablePluginActions(richText);
3225 }
3226 
3227 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible)
3228 {
3229     if (visible) {
3230         enableHtml();
3231     } else {
3232         disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
3233     }
3234 }
3235 
3236 void KMComposerWin::slotAutoSpellCheckingToggled(bool on)
3237 {
3238     mAutoSpellCheckingAction->setChecked(on);
3239     if (on != mComposerBase->editor()->checkSpellingEnabled()) {
3240         mComposerBase->editor()->setCheckSpellingEnabled(on);
3241     }
3242     if (on != mEdtSubject->checkSpellingEnabled()) {
3243         mEdtSubject->setCheckSpellingEnabled(on);
3244     }
3245     mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on);
3246 }
3247 
3248 void KMComposerWin::showAndActivateComposer()
3249 {
3250     show();
3251     raise();
3252     activateWindow();
3253 }
3254 
3255 void KMComposerWin::slotSpellCheckingStatus(const QString &status)
3256 {
3257     mStatusbarLabel->setText(status);
3258     QTimer::singleShot(2s, this, &KMComposerWin::slotSpellcheckDoneClearStatus);
3259 }
3260 
3261 void KMComposerWin::slotSpellcheckDoneClearStatus()
3262 {
3263     mStatusbarLabel->clear();
3264 }
3265 
3266 void KMComposerWin::slotIdentityChanged(uint uoid, bool initialChange)
3267 {
3268     if (!mMsg) {
3269         qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!";
3270         return;
3271     }
3272     const KIdentityManagementCore::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(uoid);
3273     if (ident.isNull()) {
3274         return;
3275     }
3276     const bool wasModified(isModified());
3277     Q_EMIT identityChanged(identity());
3278     if (!ident.fullEmailAddr().isNull()) {
3279         mEdtFrom->setText(ident.fullEmailAddr());
3280     }
3281 
3282     // make sure the From field is shown if it does not contain a valid email address
3283     if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
3284         mShowHeaders |= HDR_FROM;
3285     }
3286 
3287     // remove BCC of old identity and add BCC of new identity (if they differ)
3288     const KIdentityManagementCore::Identity &oldIdentity = KMKernel::self()->identityManager()->identityForUoidOrDefault(mId);
3289 
3290     if (ident.organization().isEmpty()) {
3291         mMsg->removeHeader<KMime::Headers::Organization>();
3292     } else {
3293         auto const organization = new KMime::Headers::Organization;
3294         organization->fromUnicodeString(ident.organization(), "utf-8");
3295         mMsg->setHeader(organization);
3296     }
3297 
3298     addFaceHeaders(ident, mMsg);
3299 
3300     if (initialChange) {
3301         if (auto hrd = mMsg->headerByType("X-KMail-Transport")) {
3302             const QString mailtransportStr = hrd->asUnicodeString();
3303             if (!mailtransportStr.isEmpty()) {
3304                 int transportId = mailtransportStr.toInt();
3305                 const Transport *transport = TransportManager::self()->transportById(transportId, false); /*don't return default transport */
3306                 if (transport) {
3307                     auto header = new KMime::Headers::Generic("X-KMail-Transport");
3308                     header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3309                     mMsg->setHeader(header);
3310                     mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3311                 } else {
3312                     if (auto hrd = mMsg->headerByType("X-KMail-Transport-Name")) {
3313                         const QString identityStrName = hrd->asUnicodeString();
3314                         const Transport *transport = TransportManager::self()->transportByName(identityStrName, true);
3315                         if (transport) {
3316                             auto header = new KMime::Headers::Generic("X-KMail-Transport");
3317                             header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3318                             mMsg->setHeader(header);
3319                             mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3320                         } else {
3321                             mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3322                         }
3323                     } else {
3324                         mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3325                     }
3326                 }
3327             }
3328         } else {
3329             const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
3330             const Transport *transport = TransportManager::self()->transportById(transportId, true);
3331             if (transport) {
3332                 auto header = new KMime::Headers::Generic("X-KMail-Transport");
3333                 header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3334                 mMsg->setHeader(header);
3335                 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3336             } else {
3337                 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3338             }
3339         }
3340     } else {
3341         const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
3342         const Transport *transport = TransportManager::self()->transportById(transportId, true);
3343         if (!transport) {
3344             mMsg->removeHeader("X-KMail-Transport");
3345             mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3346         } else {
3347             auto header = new KMime::Headers::Generic("X-KMail-Transport");
3348             header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3349             mMsg->setHeader(header);
3350             mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3351         }
3352     }
3353 
3354     const bool fccIsDisabled = ident.disabledFcc();
3355     if (fccIsDisabled) {
3356         auto header = new KMime::Headers::Generic("X-KMail-FccDisabled");
3357         header->fromUnicodeString(QStringLiteral("true"), "utf-8");
3358         mMsg->setHeader(header);
3359     } else {
3360         mMsg->removeHeader("X-KMail-FccDisabled");
3361     }
3362     mFccFolder->setEnabled(!fccIsDisabled);
3363 
3364     mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
3365     slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary());
3366     if (!mPreventFccOverwrite) {
3367         setFcc(ident.fcc());
3368     }
3369     // if unmodified, apply new template, if one is set
3370     if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) && !initialChange) {
3371         applyTemplate(uoid, mId, ident, wasModified);
3372     } else {
3373         mComposerBase->identityChanged(ident, oldIdentity, false);
3374         mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage());
3375         updateComposerAfterIdentityChanged(ident, uoid, wasModified);
3376     }
3377 }
3378 
3379 void KMComposerWin::checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident)
3380 {
3381     mNearExpiryWarning->clearInfo();
3382     mNearExpiryWarning->hide();
3383 
3384     if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
3385         if (!ident.pgpEncryptionKey().isEmpty()) {
3386             auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpEncryptionKey().constData());
3387             if (key.isNull() || !key.canEncrypt()) {
3388                 mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint",
3389                                                   "Your selected PGP key (%1) doesn't exist in your keyring or is not suitable for encryption.",
3390                                                   QString::fromUtf8(ident.pgpEncryptionKey())));
3391                 mNearExpiryWarning->setWarning(true);
3392                 mNearExpiryWarning->show();
3393             } else {
3394                 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey);
3395             }
3396         }
3397         if (!ident.pgpSigningKey().isEmpty()) {
3398             if (ident.pgpSigningKey() != ident.pgpEncryptionKey()) {
3399                 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpSigningKey().constData());
3400                 if (key.isNull() || !key.canSign()) {
3401                     mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint",
3402                                                       "Your selected PGP signing key (%1) doesn't exist in your keyring or is not suitable for signing.",
3403                                                       QString::fromUtf8(ident.pgpSigningKey())));
3404                     mNearExpiryWarning->setWarning(true);
3405                     mNearExpiryWarning->show();
3406                 } else {
3407                     mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey);
3408                 }
3409             }
3410         }
3411     }
3412 
3413     if (cryptoMessageFormat() & Kleo::AnySMIME) {
3414         if (!ident.smimeEncryptionKey().isEmpty()) {
3415             auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeEncryptionKey().constData());
3416             if (key.isNull() || !key.canEncrypt()) {
3417                 mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint",
3418                                                   "Your selected SMIME key (%1) doesn't exist in your keyring or is not suitable for encryption.",
3419                                                   QString::fromUtf8(ident.smimeEncryptionKey())));
3420                 mNearExpiryWarning->setWarning(true);
3421                 mNearExpiryWarning->show();
3422             } else {
3423                 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey);
3424             }
3425         }
3426         if (!ident.smimeSigningKey().isEmpty()) {
3427             if (ident.smimeSigningKey() != ident.smimeEncryptionKey()) {
3428                 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeSigningKey().constData());
3429                 if (key.isNull() || !key.canSign()) {
3430                     mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint",
3431                                                       "Your selected SMIME signing key (%1) doesn't exist in your keyring or is not suitable for signing.",
3432                                                       QString::fromUtf8(ident.smimeSigningKey())));
3433                     mNearExpiryWarning->setWarning(true);
3434                     mNearExpiryWarning->show();
3435                 } else {
3436                     mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey);
3437                 }
3438             }
3439         }
3440     }
3441 }
3442 
3443 void KMComposerWin::updateComposerAfterIdentityChanged(const KIdentityManagementCore::Identity &ident, uint uoid, bool wasModified)
3444 {
3445     // disable certain actions if there is no PGP user identity set
3446     // for this profile
3447     bool bPGPEncryptionKey = !ident.pgpEncryptionKey().isEmpty();
3448     bool bPGPSigningKey = !ident.pgpSigningKey().isEmpty();
3449     bool bSMIMEEncryptionKey = !ident.smimeEncryptionKey().isEmpty();
3450     bool bSMIMESigningKey = !ident.smimeSigningKey().isEmpty();
3451     if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
3452         if (bPGPEncryptionKey) {
3453             auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpEncryptionKey().constData());
3454             if (key.isNull() || !key.canEncrypt()) {
3455                 bPGPEncryptionKey = false;
3456             }
3457         }
3458         if (bPGPSigningKey) {
3459             auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpSigningKey().constData());
3460             if (key.isNull() || !key.canSign()) {
3461                 bPGPSigningKey = false;
3462             }
3463         }
3464     } else {
3465         bPGPEncryptionKey = false;
3466         bPGPSigningKey = false;
3467     }
3468 
3469     if (cryptoMessageFormat() & Kleo::AnySMIME) {
3470         if (bSMIMEEncryptionKey) {
3471             auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeEncryptionKey().constData());
3472             if (key.isNull() || !key.canEncrypt()) {
3473                 bSMIMEEncryptionKey = false;
3474             }
3475         }
3476         if (bSMIMESigningKey) {
3477             auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeSigningKey().constData());
3478             if (key.isNull() || !key.canSign()) {
3479                 bSMIMESigningKey = false;
3480             }
3481         }
3482     } else {
3483         bSMIMEEncryptionKey = false;
3484         bSMIMESigningKey = false;
3485     }
3486 
3487     bool bNewIdentityHasSigningKey = bPGPSigningKey || bSMIMESigningKey;
3488     bool bNewIdentityHasEncryptionKey = bPGPEncryptionKey || bSMIMEEncryptionKey;
3489 
3490     if (!mKeyCache->initialized()) {
3491         // We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated.
3492         mKeyCache->startKeyListing();
3493         connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this, &ident]() {
3494             checkOwnKeyExpiry(ident);
3495             runKeyResolver();
3496         });
3497     } else {
3498         checkOwnKeyExpiry(ident);
3499     }
3500 
3501     // save the state of the sign and encrypt button
3502     if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) {
3503         mLastEncryptActionState = mEncryptionState.encrypt();
3504     }
3505 
3506     mSignAction->setEnabled(bNewIdentityHasSigningKey);
3507 
3508     if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) {
3509         mLastSignActionState = sign();
3510         setSigning(false);
3511     }
3512     // restore the last state of the sign and encrypt button
3513     if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) {
3514         setSigning(mLastSignActionState);
3515     }
3516 
3517     mCryptoModuleAction->setCurrentItem(format2cb(Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat())));
3518     slotSelectCryptoModule(true);
3519 
3520     mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
3521     mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
3522     const KIdentityManagementCore::Signature sig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
3523     bool isEnabledSignature = sig.isEnabledSignature();
3524     mAppendSignature->setEnabled(isEnabledSignature);
3525     mPrependSignature->setEnabled(isEnabledSignature);
3526     mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature);
3527 
3528     mId = uoid;
3529 
3530     mEncryptionState.setPossibleEncrypt(true);
3531     changeCryptoAction();
3532     mEncryptionState.unsetOverride();
3533     mEncryptionState.setPossibleEncrypt(mEncryptionState.possibleEncrypt() && bNewIdentityHasEncryptionKey);
3534     mEncryptionState.setAutoEncrypt(ident.pgpAutoEncrypt());
3535 
3536     // make sure the From and BCC fields are shown if necessary
3537     rethinkFields(false);
3538     setModified(wasModified);
3539 
3540     if (ident.pgpAutoEncrypt() && mKeyCache->initialized()) {
3541         runKeyResolver();
3542     }
3543 }
3544 
3545 auto findSendersUid(const std::string &addrSpec, const std::vector<GpgME::UserID> &userIds)
3546 {
3547     return std::find_if(userIds.cbegin(), userIds.cend(), [&addrSpec](const auto &uid) {
3548         return uid.addrSpec() == addrSpec || (uid.addrSpec().empty() && std::string(uid.email()) == addrSpec)
3549             || (uid.addrSpec().empty() && (!uid.email() || !*uid.email()) && uid.name() == addrSpec);
3550     });
3551 }
3552 
3553 std::unique_ptr<Kleo::KeyResolverCore> KMComposerWin::fillKeyResolver()
3554 {
3555     const auto ident = identity();
3556     auto keyResolverCore = std::make_unique<Kleo::KeyResolverCore>(true, sign());
3557 
3558     keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown);
3559 
3560     QStringList signingKeys, encryptionKeys;
3561 
3562     if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
3563         if (!ident.pgpSigningKey().isEmpty()) {
3564             signingKeys.push_back(QLatin1StringView(ident.pgpSigningKey()));
3565         }
3566         if (!ident.pgpEncryptionKey().isEmpty()) {
3567             encryptionKeys.push_back(QLatin1StringView(ident.pgpEncryptionKey()));
3568         }
3569     }
3570 
3571     if (cryptoMessageFormat() & Kleo::AnySMIME) {
3572         if (!ident.smimeSigningKey().isEmpty()) {
3573             signingKeys.push_back(QLatin1StringView(ident.smimeSigningKey()));
3574         }
3575         if (!ident.smimeEncryptionKey().isEmpty()) {
3576             encryptionKeys.push_back(QLatin1StringView(ident.smimeEncryptionKey()));
3577         }
3578     }
3579 
3580     keyResolverCore->setSender(ident.fullEmailAddr());
3581     keyResolverCore->setSigningKeys(signingKeys);
3582     keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{keyResolverCore->normalizedSender(), encryptionKeys}}}});
3583 
3584     QStringList recipients;
3585     const auto lst = mComposerBase->recipientsEditor()->lines();
3586     for (auto line : lst) {
3587         auto recipient = line->data().dynamicCast<MessageComposer::Recipient>();
3588         recipients.push_back(recipient->email());
3589     }
3590 
3591     keyResolverCore->setRecipients(recipients);
3592     return keyResolverCore;
3593 }
3594 
3595 void KMComposerWin::slotEncryptionButtonIconUpdate()
3596 {
3597     const auto state = mEncryptionState.encrypt();
3598     const auto setByUser = mEncryptionState.override();
3599     const auto acceptedSolution = mEncryptionState.acceptedSolution();
3600 
3601     auto icon = QIcon::fromTheme(QStringLiteral("document-encrypt"));
3602     QString tooltip;
3603     if (state) {
3604         tooltip = i18nc("@info:tooltip", "Encrypt");
3605     } else {
3606         tooltip = i18nc("@info:tooltip", "Not Encrypt");
3607         icon = QIcon::fromTheme(QStringLiteral("document-decrypt"));
3608     }
3609 
3610     if (acceptedSolution) {
3611         auto overlay = QIcon::fromTheme(QStringLiteral("emblem-added"));
3612         if (state) {
3613             overlay = QIcon::fromTheme(QStringLiteral("emblem-checked"));
3614         }
3615         icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner);
3616     } else {
3617         if (state && setByUser) {
3618             auto overlay = QIcon::fromTheme(QStringLiteral("emblem-warning"));
3619             icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner);
3620         }
3621     }
3622     mEncryptAction->setIcon(icon);
3623     mEncryptAction->setToolTip(tooltip);
3624 }
3625 
3626 void KMComposerWin::runKeyResolver()
3627 {
3628     const auto ident = identity();
3629     auto keyResolverCore = fillKeyResolver();
3630     auto result = keyResolverCore->resolve();
3631 
3632     QStringList autocryptKeys;
3633     QStringList gossipKeys;
3634 
3635     if (!(result.flags & Kleo::KeyResolverCore::AllResolved)) {
3636         if (result.flags & Kleo::KeyResolverCore::OpenPGPOnly && ident.autocryptEnabled()) {
3637             bool allResolved = true;
3638             const auto storage = MessageCore::AutocryptStorage::self();
3639             for (const auto &recipient : result.solution.encryptionKeys.keys()) {
3640                 const auto key = result.solution.encryptionKeys[recipient];
3641                 if (key.size() > 0) { // There are already keys found
3642                     continue;
3643                 }
3644                 if (recipient == keyResolverCore->normalizedSender()) { // Don't care about own key as we show warnings in another way
3645                     continue;
3646                 }
3647                 const auto rec = storage->getRecipient(recipient.toUtf8());
3648                 GpgME::Key autocryptKey;
3649                 if (rec) {
3650                     const auto key = rec->gpgKey();
3651                     if (!key.isBad() && key.canEncrypt()) {
3652                         autocryptKey = key;
3653                     } else {
3654                         const auto gossipKey = rec->gossipKey();
3655                         if (!gossipKey.isBad() && gossipKey.canEncrypt()) {
3656                             gossipKeys.push_back(recipient);
3657                             autocryptKey = gossipKey;
3658                         }
3659                     }
3660                 }
3661                 if (!autocryptKey.isNull()) {
3662                     autocryptKeys.push_back(recipient);
3663                     result.solution.encryptionKeys[recipient].push_back(autocryptKey);
3664                 } else {
3665                     allResolved = false;
3666                 }
3667             }
3668             if (allResolved) {
3669                 result.flags = Kleo::KeyResolverCore::SolutionFlags(result.flags | Kleo::KeyResolverCore::AllResolved);
3670             }
3671         }
3672     }
3673 
3674     const auto lst = mComposerBase->recipientsEditor()->lines();
3675 
3676     if (lst.size() == 1) {
3677         const auto line = qobject_cast<MessageComposer::RecipientLineNG *>(lst.first());
3678         if (line->recipientsCount() == 0) {
3679             mEncryptionState.setAcceptedSolution(false);
3680             return;
3681         }
3682     }
3683 
3684     mEncryptionState.setAcceptedSolution(result.flags & Kleo::KeyResolverCore::AllResolved);
3685 
3686     for (auto line_ : lst) {
3687         auto line = qobject_cast<MessageComposer::RecipientLineNG *>(line_);
3688         Q_ASSERT(line);
3689         auto recipient = line->data().dynamicCast<MessageComposer::Recipient>();
3690 
3691         QString dummy;
3692         QString addrSpec;
3693         if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
3694             addrSpec = recipient->email();
3695         }
3696 
3697         auto resolvedKeys = result.solution.encryptionKeys[addrSpec];
3698         GpgME::Key key;
3699         if (resolvedKeys.size() == 0) { // no key found for recipient
3700             // Search for any key, also for not accepted ons, to at least give the user more info.
3701             key = Kleo::KeyCache::instance()->findBestByMailBox(addrSpec.toUtf8().constData(), GpgME::UnknownProtocol, Kleo::KeyCache::KeyUsage::Encrypt);
3702             key.update(); // We need tofu information for key.
3703             recipient->setKey(key);
3704         } else { // A key was found for recipient
3705             key = resolvedKeys.front();
3706             if (recipient->key().primaryFingerprint() != key.primaryFingerprint()) {
3707                 key.update(); // We need tofu information for key.
3708                 recipient->setKey(key);
3709             }
3710         }
3711 
3712         const bool autocryptKey = autocryptKeys.contains(addrSpec);
3713         const bool gossipKey = gossipKeys.contains(addrSpec);
3714         annotateRecipientEditorLineWithCryptoInfo(line, autocryptKey, gossipKey);
3715 
3716         if (!key.isNull()) {
3717             mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::EncryptionKey);
3718         }
3719     }
3720 }
3721 
3722 void KMComposerWin::annotateRecipientEditorLineWithCryptoInfo(MessageComposer::RecipientLineNG *line, bool autocryptKey, bool gossipKey)
3723 {
3724     auto recipient = line->data().dynamicCast<MessageComposer::Recipient>();
3725     const auto key = recipient->key();
3726 
3727     const auto showCryptoIndicator = KMailSettings::self()->showCryptoLabelIndicator();
3728     const auto hasOverride = mEncryptionState.hasOverride();
3729     const auto encrypt = mEncryptionState.encrypt();
3730 
3731     const bool showPositiveIcons = showCryptoIndicator && encrypt;
3732     const bool showAllIcons = showCryptoIndicator && hasOverride && encrypt;
3733 
3734     QString dummy;
3735     QString addrSpec;
3736     bool invalidEmail = false;
3737     if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
3738         invalidEmail = true;
3739         addrSpec = recipient->email();
3740     }
3741 
3742     if (key.isNull()) {
3743         recipient->setEncryptionAction(Kleo::Impossible);
3744         if (showAllIcons && !invalidEmail) {
3745             const auto icon = QIcon::fromTheme(QStringLiteral("emblem-error"));
3746             line->setIcon(icon, i18nc("@info:tooltip", "No key found for the recipient."));
3747         } else {
3748             line->setIcon(QIcon());
3749         }
3750         line->setProperty("keyStatus", invalidEmail ? InProgress : NoKey);
3751         return;
3752     }
3753 
3754     KMComposerWin::CryptoKeyState keyState = KeyOk;
3755 
3756     if (recipient->encryptionAction() != Kleo::DoIt) {
3757         recipient->setEncryptionAction(Kleo::DoIt);
3758     }
3759 
3760     if (autocryptKey) { // We found an Autocrypt key for recipient
3761         QIcon icon = QIcon::fromTheme(QStringLiteral("emblem-success"));
3762         QString tooltip;
3763         const auto storage = MessageCore::AutocryptStorage::self();
3764         const auto rec = storage->getRecipient(addrSpec.toUtf8());
3765         if (gossipKey) { // We found an Autocrypt gossip key for recipient
3766             icon = QIcon::fromTheme(QStringLiteral("emblem-informations"));
3767             tooltip = i18nc("@info:tooltip",
3768                             "Autocrypt gossip key is used for this recipient. We got this key from 3rd party recipients. "
3769                             "This key is not verified.");
3770         } else if (rec->prefer_encrypt()) {
3771             tooltip = i18nc("@info:tooltip",
3772                             "Autocrypt key is used for this recipient. "
3773                             "This key is not verified. The recipient prefers encrypted replies.");
3774         } else {
3775             tooltip = i18nc("@info:tooltip",
3776                             "Autocrypt key is used for this recipient. "
3777                             "This key is not verified. The recipient does not prefer encrypted replies.");
3778         }
3779         if (showAllIcons) {
3780             line->setIcon(icon, tooltip);
3781         } else {
3782             line->setIcon(QIcon());
3783         }
3784 
3785     } else {
3786         QIcon icon;
3787         QString tooltip;
3788 
3789         const auto uids = key.userIDs();
3790         const auto _uid = findSendersUid(addrSpec.toStdString(), uids);
3791         GpgME::UserID uid;
3792         if (_uid == uids.cend()) {
3793             uid = key.userID(0);
3794         } else {
3795             uid = *_uid;
3796         }
3797 
3798         const auto trustLevel = Kleo::trustLevel(uid);
3799         switch (trustLevel) {
3800         case Kleo::Level0:
3801             if (uid.tofuInfo().isNull()) {
3802                 tooltip = i18nc("@info:tooltip",
3803                                 "The encryption key is not trusted. It hasn't enough validity. "
3804                                 "You can sign the key, if you communicated the fingerprint by another channel. "
3805                                 "Click the icon for details.");
3806                 keyState = NoKey;
3807             } else {
3808                 switch (uid.tofuInfo().validity()) {
3809                 case GpgME::TofuInfo::NoHistory:
3810                     tooltip = i18nc("@info:tooltip",
3811                                     "The encryption key is not trusted. "
3812                                     "It hasn't been used anywhere to guarantee it belongs to the stated person. "
3813                                     "By using the key will be trusted more. "
3814                                     "Or you can sign the key, if you communicated the fingerprint by another channel. "
3815                                     "Click the icon for details.");
3816                     break;
3817                 case GpgME::TofuInfo::Conflict:
3818                     tooltip = i18nc("@info:tooltip",
3819                                     "The encryption key is not trusted. It has conflicting TOFU data. "
3820                                     "Click the icon for details.");
3821                     keyState = NoKey;
3822                     break;
3823                 case GpgME::TofuInfo::ValidityUnknown:
3824                     tooltip = i18nc("@info:tooltip",
3825                                     "The encryption key is not trusted. It has unknown validity in TOFU data. "
3826                                     "Click the icon for details.");
3827                     keyState = NoKey;
3828                     break;
3829                 default:
3830                     tooltip = i18nc("@info:tooltip",
3831                                     "The encryption key is not trusted. The key is marked as bad. "
3832                                     "Click the icon for details.");
3833                     keyState = NoKey;
3834                 }
3835             }
3836             break;
3837         case Kleo::Level1:
3838             tooltip = i18nc("@info:tooltip",
3839                             "The encryption key is only marginally trusted and hasn't been used enough time to guarantee it belongs to the stated person. "
3840                             "By using the key will be trusted more. "
3841                             "Or you can sign the key, if you communicated the fingerprint by another channel. "
3842                             "Click the icon for details.");
3843             break;
3844         case Kleo::Level2:
3845             if (uid.tofuInfo().isNull()) {
3846                 tooltip = i18nc("@info:tooltip",
3847                                 "The encryption key is only marginally trusted. "
3848                                 "You can sign the key, if you communicated the fingerprint by another channel. "
3849                                 "Click the icon for details.");
3850             } else {
3851                 tooltip =
3852                     i18nc("@info:tooltip",
3853                           "The encryption key is only marginally trusted, but has been used enough times to be very likely controlled by the stated person. "
3854                           "By using the key will be trusted more. "
3855                           "Or you can sign the key, if you communicated the fingerprint by another channel. "
3856                           "Click the icon for details.");
3857             }
3858             break;
3859         case Kleo::Level3:
3860             tooltip = i18nc("@info:tooltip",
3861                             "The encryption key is fully trusted. You can raise the security level, by signing the key. "
3862                             "Click the icon for details.");
3863             break;
3864         case Kleo::Level4:
3865             tooltip = i18nc("@info:tooltip",
3866                             "The encryption key is ultimately trusted or is signed by another ultimately trusted key. "
3867                             "Click the icon for details.");
3868             break;
3869         default:
3870             Q_UNREACHABLE();
3871         }
3872 
3873         if (keyState == NoKey) {
3874             mEncryptionState.setAcceptedSolution(false);
3875             if (showAllIcons) {
3876                 line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")), tooltip);
3877             } else {
3878                 line->setIcon(QIcon());
3879             }
3880         } else if (trustLevel == Kleo::Level0 && encrypt) {
3881             line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")), tooltip);
3882         } else if (showPositiveIcons) {
3883             // Magically, the icon name maps precisely to each trust level
3884             // line->setIcon(QIcon::fromTheme(QStringLiteral("gpg-key-trust-level-%1").arg(trustLevel)), tooltip);
3885             line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-success")), tooltip);
3886         } else {
3887             line->setIcon(QIcon());
3888         }
3889     }
3890 
3891     if (line->property("keyStatus") != keyState) {
3892         line->setProperty("keyStatus", keyState);
3893     }
3894 }
3895 
3896 void KMComposerWin::slotSpellcheckConfig()
3897 {
3898     static_cast<KMComposerEditorNg *>(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc"));
3899 }
3900 
3901 void KMComposerWin::slotEditToolbars()
3902 {
3903     QPointer<KEditToolBar> dlg = new KEditToolBar(guiFactory(), this);
3904 
3905     connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars);
3906 
3907     dlg->exec();
3908     delete dlg;
3909 }
3910 
3911 void KMComposerWin::slotUpdateToolbars()
3912 {
3913     createGUI(QStringLiteral("kmcomposerui.rc"));
3914     applyMainWindowSettings(KMKernel::self()->config()->group(QStringLiteral("Composer")));
3915 }
3916 
3917 void KMComposerWin::slotEditKeys()
3918 {
3919     KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this);
3920 }
3921 
3922 void KMComposerWin::setFocusToEditor()
3923 {
3924     // The cursor position is already set by setMsg(), so we only need to set the
3925     // focus here.
3926     mComposerBase->editor()->setFocus();
3927 }
3928 
3929 void KMComposerWin::setFocusToSubject()
3930 {
3931     mEdtSubject->setFocus();
3932 }
3933 
3934 void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode)
3935 {
3936     KMailSettings::self()->setCompletionMode(static_cast<int>(mode));
3937 
3938     // sync all the lineedits to the same completion mode
3939     mEdtFrom->setCompletionMode(mode);
3940     mComposerBase->recipientsEditor()->setCompletionMode(mode);
3941 }
3942 
3943 void KMComposerWin::slotConfigChanged()
3944 {
3945     readConfig(true /*reload*/);
3946     mComposerBase->updateAutoSave();
3947     rethinkFields();
3948     slotWordWrapToggled(mWordWrapAction->isChecked());
3949 }
3950 
3951 /*
3952  * checks if the drafts-folder has been deleted
3953  * that is not nice so we set the system-drafts-folder
3954  */
3955 void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col)
3956 {
3957     qCDebug(KMAIL_LOG) << "you killed me.";
3958     // TODO: need to handle templates here?
3959     if ((mFolder.isValid()) && (col.id() == mFolder.id())) {
3960         mFolder = CommonKernel->draftsCollectionFolder();
3961         qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id();
3962     } else if (col.id() == mFccFolder->collection().id()) {
3963         qCDebug(KMAIL_LOG) << "FCC was removed " << col.id();
3964         mFccFolder->setCollection(CommonKernel->sentCollectionFolder());
3965         mIncorrectIdentityFolderWarning->fccIsInvalid();
3966     }
3967 }
3968 
3969 void KMComposerWin::slotOverwriteModeChanged()
3970 {
3971     const bool overwriteMode = mComposerBase->editor()->overwriteMode();
3972     mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1);
3973     mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode);
3974 }
3975 
3976 void KMComposerWin::slotCursorPositionChanged()
3977 {
3978     // Change Line/Column info in status bar
3979     const int line = mComposerBase->editor()->linePosition() + 1;
3980     const int col = mComposerBase->editor()->columnNumber() + 1;
3981     QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line);
3982     mCursorLineLabel->setText(temp);
3983     temp = i18n(" Column: %1 ", col);
3984     mCursorColumnLabel->setText(temp);
3985 
3986     // Show link target in status bar
3987     if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) {
3988         const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1StringView(" -> ")
3989             + mComposerBase->editor()->composerControler()->currentLinkUrl();
3990         mStatusbarLabel->setText(text);
3991     } else {
3992         mStatusbarLabel->clear();
3993     }
3994 }
3995 
3996 void KMComposerWin::recipientEditorSizeHintChanged()
3997 {
3998     QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize);
3999 }
4000 
4001 void KMComposerWin::setMaximumHeaderSize()
4002 {
4003     mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
4004 }
4005 
4006 void KMComposerWin::updateSignatureAndEncryptionStateIndicators()
4007 {
4008     mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(sign(), mEncryptionState.encrypt());
4009     if (sign() || mEncryptionState.encrypt()) {
4010         mComposerBase->editor()->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
4011         mComposerBase->editor()->setProperty("_breeze_force_frame", true);
4012     } else {
4013         mComposerBase->editor()->setProperty("_breeze_borders_sides", QVariant{});
4014         mComposerBase->editor()->setProperty("_breeze_force_frame", false);
4015     }
4016 }
4017 
4018 void KMComposerWin::slotDictionaryLanguageChanged(const QString &language)
4019 {
4020     mComposerBase->dictionary()->setCurrentByDictionary(language);
4021 }
4022 
4023 void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection)
4024 {
4025     mComposerBase->setFcc(collection);
4026     mComposerBase->editor()->document()->setModified(true);
4027 }
4028 
4029 void KMComposerWin::slotSaveAsFile()
4030 {
4031     auto job = new SaveAsFileJob(this);
4032     job->setParentWidget(this);
4033     job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
4034     job->setTextDocument(mComposerBase->editor()->document());
4035     job->start();
4036     // not necessary to delete it. It done in SaveAsFileJob
4037 }
4038 
4039 void KMComposerWin::slotAttachMissingFile()
4040 {
4041     mComposerBase->attachmentController()->showAddAttachmentFileDialog();
4042 }
4043 
4044 void KMComposerWin::slotVerifyMissingAttachmentTimeout()
4045 {
4046     if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) {
4047         mAttachmentMissing->animatedShow();
4048     }
4049 }
4050 
4051 void KMComposerWin::slotExplicitClosedMissingAttachment()
4052 {
4053     if (mVerifyMissingAttachment) {
4054         mVerifyMissingAttachment->stop();
4055         delete mVerifyMissingAttachment;
4056         mVerifyMissingAttachment = nullptr;
4057     }
4058 }
4059 
4060 void KMComposerWin::addExtraCustomHeaders(const QMap<QByteArray, QString> &headers)
4061 {
4062     mExtraHeaders = headers;
4063 }
4064 
4065 bool KMComposerWin::processModifyText(QKeyEvent *event)
4066 {
4067     return mPluginEditorManagerInterface->processProcessKeyEvent(event);
4068 }
4069 
4070 MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus KMComposerWin::convertPlainText(MessageComposer::TextPart *textPart)
4071 {
4072     return mPluginEditorConvertTextManagerInterface->convertTextToFormat(textPart);
4073 }
4074 
4075 void KMComposerWin::slotExternalEditorStarted()
4076 {
4077     mComposerBase->identityCombo()->setEnabled(false);
4078     mExternalEditorWarning->show();
4079 }
4080 
4081 void KMComposerWin::slotExternalEditorClosed()
4082 {
4083     mComposerBase->identityCombo()->setEnabled(true);
4084     mExternalEditorWarning->hide();
4085 }
4086 
4087 void KMComposerWin::slotInsertShortUrl(const QString &url)
4088 {
4089     mComposerBase->editor()->composerControler()->insertLink(url);
4090 }
4091 
4092 void KMComposerWin::slotTransportChanged()
4093 {
4094     mComposerBase->editor()->document()->setModified(true);
4095 }
4096 
4097 void KMComposerWin::slotFollowUpMail(bool toggled)
4098 {
4099     if (toggled) {
4100         QPointer<MessageComposer::FollowUpReminderSelectDateDialog> dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this);
4101         if (dlg->exec()) {
4102             mComposerBase->setFollowUpDate(dlg->selectedDate());
4103             mComposerBase->setFollowUpCollection(dlg->collection());
4104         } else {
4105             mFollowUpToggleAction->setChecked(false);
4106         }
4107         delete dlg;
4108     } else {
4109         mComposerBase->clearFollowUp();
4110     }
4111 }
4112 
4113 void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b)
4114 {
4115     mSnippetWidget->setVisible(b);
4116     mSnippetSplitterCollapser->setVisible(b);
4117 }
4118 
4119 void KMComposerWin::slotOverwriteModeWasChanged(bool state)
4120 {
4121     mComposerBase->editor()->setCursorWidth(state ? 5 : 1);
4122     mComposerBase->editor()->setOverwriteMode(state);
4123 }
4124 
4125 QList<KToggleAction *> KMComposerWin::customToolsList() const
4126 {
4127     return mCustomToolsWidget->actionList();
4128 }
4129 
4130 QList<QAction *> KMComposerWin::pluginToolsActionListForPopupMenu() const
4131 {
4132     return mPluginEditorManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu)
4133         + mPluginEditorConvertTextManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu);
4134 }
4135 
4136 void KMComposerWin::slotRecipientEditorLineAdded(KPIM::MultiplyingLine *line_)
4137 {
4138     auto line = qobject_cast<MessageComposer::RecipientLineNG *>(line_);
4139     Q_ASSERT(line);
4140 
4141     connect(line, &MessageComposer::RecipientLineNG::countChanged, this, [this, line]() {
4142         this->slotRecipientAdded(line);
4143     });
4144     connect(line, &MessageComposer::RecipientLineNG::iconClicked, this, [this, line]() {
4145         this->slotRecipientLineIconClicked(line);
4146     });
4147     connect(line, &MessageComposer::RecipientLineNG::destroyed, this, &KMComposerWin::slotRecipientEditorFocusChanged, Qt::QueuedConnection);
4148     connect(
4149         line,
4150         &MessageComposer::RecipientLineNG::activeChanged,
4151         this,
4152         [this, line]() {
4153             this->slotRecipientFocusLost(line);
4154         },
4155         Qt::QueuedConnection);
4156 
4157     slotRecipientEditorFocusChanged();
4158 }
4159 
4160 bool KMComposerWin::sign() const
4161 {
4162     return mSignAction->isChecked();
4163 }
4164 
4165 void KMComposerWin::slotRecipientEditorFocusChanged()
4166 {
4167     if (!mEncryptionState.possibleEncrypt()) {
4168         return;
4169     }
4170 
4171     if (mKeyCache->initialized()) {
4172         mRunKeyResolverTimer->stop();
4173         runKeyResolver();
4174     }
4175 }
4176 
4177 void KMComposerWin::slotRecipientLineIconClicked(MessageComposer::RecipientLineNG *line)
4178 {
4179     const auto recipient = line->data().dynamicCast<MessageComposer::Recipient>();
4180 
4181     if (!recipient->key().isNull()) {
4182         const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
4183         if (exec.isEmpty()
4184             || !QProcess::startDetached(exec,
4185                                         {QStringLiteral("--query"),
4186                                          QString::fromLatin1(recipient->key().primaryFingerprint()),
4187                                          QStringLiteral("--parent-windowid"),
4188                                          QString::number(winId())})) {
4189             qCWarning(KMAIL_LOG) << "Unable to execute kleopatra";
4190         }
4191         return;
4192     }
4193 
4194     const auto msg = i18nc(
4195         "if in your language something like "
4196         "'certificate(s)' is not possible please "
4197         "use the plural in the translation",
4198         "<qt>No valid and trusted encryption certificate was "
4199         "found for \"%1\".<br/><br/>"
4200         "Select the certificate(s) which should "
4201         "be used for this recipient. If there is no suitable certificate in the list "
4202         "you can also search for external certificates by clicking the button: "
4203         "search for external certificates.</qt>",
4204         recipient->name().isEmpty() ? recipient->email() : recipient->name());
4205 
4206     const bool opgp = containsOpenPGP(cryptoMessageFormat());
4207     const bool x509 = containsSMIME(cryptoMessageFormat());
4208 
4209     QPointer<Kleo::KeySelectionDialog> dlg = new Kleo::KeySelectionDialog(
4210         i18n("Encryption Key Selection"),
4211         msg,
4212         recipient->email(),
4213         {},
4214         Kleo::KeySelectionDialog::ValidEncryptionKeys | (opgp ? Kleo::KeySelectionDialog::OpenPGPKeys : 0) | (x509 ? Kleo::KeySelectionDialog::SMIMEKeys : 0),
4215         false, // multi-selection
4216         false); // "remember choice" box;
4217 
4218     dlg->open();
4219 
4220     connect(dlg, &QDialog::accepted, this, [dlg, recipient, line, this]() {
4221         auto key = dlg->selectedKey();
4222         key.update(); // We need tofu information for key.
4223         recipient->setKey(key);
4224         annotateRecipientEditorLineWithCryptoInfo(line, false, false);
4225     });
4226 }
4227 
4228 void KMComposerWin::slotRecipientAdded(MessageComposer::RecipientLineNG *line)
4229 {
4230     // Encryption is possible not possible to find encryption keys.
4231     if (!mEncryptionState.possibleEncrypt()) {
4232         return;
4233     }
4234 
4235     if (line->recipientsCount() == 0) {
4236         return;
4237     }
4238 
4239     if (!mKeyCache->initialized()) {
4240         if (line->property("keyLookupJob").toBool()) {
4241             return;
4242         }
4243 
4244         line->setProperty("keyLookupJob", true);
4245         // We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated.
4246         connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this, line]() {
4247             slotRecipientAdded(line);
4248         });
4249         return;
4250     }
4251 
4252     if (mKeyCache->initialized()) {
4253         mRunKeyResolverTimer->start();
4254     }
4255 }
4256 
4257 void KMComposerWin::slotRecipientFocusLost(MessageComposer::RecipientLineNG *line)
4258 {
4259     // Not possible to find encryption keys.
4260     if (!mEncryptionState.possibleEncrypt()) {
4261         return;
4262     }
4263 
4264     if (line->recipientsCount() == 0) {
4265         return;
4266     }
4267 
4268     if (mKeyCache->initialized()) {
4269         mRunKeyResolverTimer->start();
4270     }
4271 }
4272 
4273 void KMComposerWin::slotIdentityDeleted(uint uoid)
4274 {
4275     if (currentIdentity() == uoid) {
4276         mIncorrectIdentityFolderWarning->identityInvalid();
4277     }
4278 }
4279 
4280 void KMComposerWin::slotTransportRemoved(int id, const QString &name)
4281 {
4282     Q_UNUSED(name)
4283     if (mComposerBase->transportComboBox()->currentTransportId() == id) {
4284         mIncorrectIdentityFolderWarning->mailTransportIsInvalid();
4285     }
4286 }
4287 
4288 void KMComposerWin::slotSelectionChanged()
4289 {
4290     Q_EMIT mPluginEditorManagerInterface->textSelectionChanged(mRichTextEditorWidget->editor()->textCursor().hasSelection());
4291 }
4292 
4293 void KMComposerWin::slotMessage(const QString &str)
4294 {
4295     KMessageBox::information(this, str, i18n("Plugin Editor Information"));
4296 }
4297 
4298 void KMComposerWin::slotEditorPluginInsertText(const QString &str)
4299 {
4300     mGlobalAction->slotInsertText(str);
4301 }
4302 
4303 #include "moc_kmcomposerwin.cpp"