1 /*
2  * This file is part of KMail.
3  * SPDX-FileCopyrightText: 2011-2021 Laurent Montel <montel@kde.org>
4  *
5  * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
6  *
7  * Based on KMail code by:
8  * SPDX-FileCopyrightText: 1997 Markus Wuebben <markus.wuebben@kde.org>
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12 #include "kmcomposerwin.h"
13 // KMail includes
14 #include "attachment/attachmentcontroller.h"
15 #include "attachment/attachmentview.h"
16 #include "codec/codecaction.h"
17 #include "custommimeheader.h"
18 #include "editor/kmcomposereditorng.h"
19 #include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h"
20 #include "editor/plugininterface/kmailplugineditorconverttextmanagerinterface.h"
21 #include "editor/plugininterface/kmailplugineditorinitmanagerinterface.h"
22 #include "editor/plugininterface/kmailplugineditormanagerinterface.h"
23 #include "editor/plugininterface/kmailplugingrammareditormanagerinterface.h"
24 #include "editor/potentialphishingemail/potentialphishingemailjob.h"
25 #include "editor/potentialphishingemail/potentialphishingemailwarning.h"
26 #include "editor/warningwidgets/attachmentaddedfromexternalwarning.h"
27 #include "editor/warningwidgets/incorrectidentityfolderwarning.h"
28 #include "editor/warningwidgets/toomanyrecipientswarning.h"
29 #include "job/addressvalidationjob.h"
30 #include "job/createnewcontactjob.h"
31 #include "job/dndfromarkjob.h"
32 #include "job/saveasfilejob.h"
33 #include "job/savedraftjob.h"
34 #include "kmail_debug.h"
35 #include "kmcommands.h"
36 #include "kmcomposercreatenewcomposerjob.h"
37 #include "kmcomposerglobalaction.h"
38 #include "kmcomposerupdatetemplatejob.h"
39 #include "kmkernel.h"
40 #include "kmmainwidget.h"
41 #include "kmmainwin.h"
42 #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff...
43 #include "settings/kmailsettings.h"
44 #include "templatesconfiguration_kfg.h"
45 #include "undosend/undosendmanager.h"
46 #include "util.h"
47 #include "validatesendmailshortcut.h"
48 #include "warningwidgets/attachmentmissingwarning.h"
49 #include "warningwidgets/externaleditorwarning.h"
50 #include "widgets/cryptostateindicatorwidget.h"
51 #include "widgets/kactionmenutransport.h"
52 
53 #include <Akonadi/ChangeRecorder>
54 #include <Akonadi/Contact/ContactGroupExpandJob>
55 #include <Akonadi/ItemFetchJob>
56 #include <Akonadi/KMime/MessageFlags>
57 #include <Akonadi/KMime/MessageStatus>
58 #include <Akonadi/Monitor>
59 
60 #include <KContacts/VCardConverter>
61 
62 #include <KIdentityManagement/Identity>
63 #include <KIdentityManagement/IdentityCombo>
64 #include <KIdentityManagement/IdentityManager>
65 #include <KIdentityManagement/Signature>
66 
67 #include <KMime/Message>
68 
69 #include <KPIMTextEdit/EditorUtil>
70 #include <KPIMTextEdit/RichTextComposerActions>
71 #include <KPIMTextEdit/RichTextComposerControler>
72 #include <KPIMTextEdit/RichTextComposerImages>
73 #include <KPIMTextEdit/RichTextEditorWidget>
74 #include <KPIMTextEdit/RichTextExternalComposer>
75 
76 #include <Libkdepim/ProgressStatusBarWidget>
77 #include <Libkdepim/StatusbarProgressWidget>
78 
79 #include <KCursorSaver>
80 
81 #include <Libkleo/KeySelectionDialog>
82 #include <Libkleo/ProgressDialog>
83 
84 #include <MailCommon/FolderCollectionMonitor>
85 #include <MailCommon/FolderRequester>
86 #include <MailCommon/FolderSettings>
87 #include <MailCommon/MailKernel>
88 #include <MailCommon/SnippetTreeView>
89 #include <MailCommon/SnippetsManager>
90 
91 #include <MailTransport/Transport>
92 #include <MailTransport/TransportComboBox>
93 #include <MailTransport/TransportManager>
94 
95 #include <MessageComposer/AttachmentModel>
96 #include <MessageComposer/Composer>
97 #include <MessageComposer/ComposerLineEdit>
98 #include <MessageComposer/ComposerViewInterface>
99 #include <MessageComposer/ConvertSnippetVariablesJob>
100 #include <MessageComposer/DraftStatus>
101 #include <MessageComposer/FollowUpReminderSelectDateDialog>
102 #include <MessageComposer/FollowupReminder>
103 #include <MessageComposer/FollowupReminderCreateJob>
104 #include <MessageComposer/GlobalPart>
105 #include <MessageComposer/InfoPart>
106 #include <MessageComposer/InsertTextFileJob>
107 #include <MessageComposer/Kleo_Util>
108 #include <MessageComposer/MessageComposerSettings>
109 #include <MessageComposer/MessageHelper>
110 #include <MessageComposer/PluginActionType>
111 #include <MessageComposer/PluginEditorCheckBeforeSendParams>
112 #include <MessageComposer/PluginEditorConverterBeforeConvertingData>
113 #include <MessageComposer/PluginEditorConverterInitialData>
114 #include <MessageComposer/PluginEditorInterface>
115 #include <MessageComposer/RecipientsEditor>
116 #include <MessageComposer/RichTextComposerSignatures>
117 #include <MessageComposer/SendLaterDialog>
118 #include <MessageComposer/SendLaterInfo>
119 #include <MessageComposer/SendLaterUtil>
120 #include <MessageComposer/SignatureController>
121 #include <MessageComposer/StatusBarLabelToggledState>
122 #include <MessageComposer/TextPart>
123 #include <MessageComposer/Util>
124 
125 #include <Sonnet/DictionaryComboBox>
126 
127 #include <MessageCore/AttachmentPart>
128 #include <MessageCore/AutocryptStorage>
129 #include <MessageCore/MessageCoreSettings>
130 #include <MessageCore/NodeHelper>
131 #include <MessageCore/StringUtil>
132 
133 #include <MessageViewer/MessageViewerSettings>
134 #include <MessageViewer/Stl_Util>
135 
136 #include <MimeTreeParser/NodeHelper>
137 #include <MimeTreeParser/ObjectTreeParser>
138 #include <MimeTreeParser/SimpleObjectTreeSource>
139 
140 #include <PimCommon/CustomToolsPluginManager>
141 #include <PimCommon/CustomToolsWidgetng>
142 #include <PimCommon/KActionMenuChangeCase>
143 #include <PimCommon/LineEditWithAutoCorrection>
144 
145 #include <TemplateParser/TemplateParserJob>
146 #include <TemplateParser/TemplatesConfiguration>
147 
148 #include <QGpgME/ExportJob>
149 #include <QGpgME/KeyForMailboxJob>
150 #include <QGpgME/Protocol>
151 
152 // KDE Frameworks includes
153 #include <KActionCollection>
154 #include <KActionMenu>
155 #include <KCharsets>
156 #include <KConfigGroup>
157 #include <KEditToolBar>
158 #include <KEmailAddress>
159 #include <KEncodingFileDialog>
160 #include <KIO/JobUiDelegate>
161 #include <KIconUtils>
162 #include <KMessageBox>
163 #include <KRecentFilesAction>
164 #include <KShortcutsDialog>
165 #include <KSplitterCollapserButton>
166 #include <KStandardShortcut>
167 #include <KToggleAction>
168 #include <KToolBar>
169 #include <KXMLGUIFactory>
170 #include <QDBusConnection>
171 // Qt includes
172 #include <QAction>
173 #include <QApplication>
174 #include <QCheckBox>
175 #include <QClipboard>
176 #include <QFontDatabase>
177 #include <QInputDialog>
178 #include <QMenu>
179 #include <QMenuBar>
180 #include <QMimeData>
181 #include <QPointer>
182 #include <QShortcut>
183 #include <QSplitter>
184 #include <QStandardPaths>
185 #include <QStatusBar>
186 #include <QUrlQuery>
187 
188 // GPGME
189 #include <gpgme++/key.h>
190 #include <gpgme++/keylistresult.h>
191 
192 #include <KDialogJobUiDelegate>
193 #include <KIO/CommandLauncherJob>
194 #include <chrono>
195 
196 using namespace std::chrono_literals;
197 
198 using MailTransport::Transport;
199 using MailTransport::TransportManager;
200 using Sonnet::DictionaryComboBox;
201 
Q_DECLARE_METATYPE(MessageComposer::Recipient::Ptr)202 Q_DECLARE_METATYPE(MessageComposer::Recipient::Ptr)
203 
204 KMail::Composer *KMail::makeComposer(const KMime::Message::Ptr &msg,
205                                      bool lastSignState,
206                                      bool lastEncryptState,
207                                      Composer::TemplateContext context,
208                                      uint identity,
209                                      const QString &textSelection,
210                                      const QString &customTemplate)
211 {
212     return KMComposerWin::create(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate);
213 }
214 
create(const KMime::Message::Ptr & msg,bool lastSignState,bool lastEncryptState,Composer::TemplateContext context,uint identity,const QString & textSelection,const QString & customTemplate)215 KMail::Composer *KMComposerWin::create(const KMime::Message::Ptr &msg,
216                                        bool lastSignState,
217                                        bool lastEncryptState,
218                                        Composer::TemplateContext context,
219                                        uint identity,
220                                        const QString &textSelection,
221                                        const QString &customTemplate)
222 {
223     return new KMComposerWin(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate);
224 }
225 
226 int KMComposerWin::s_composerNumber = 0;
227 
KMComposerWin(const KMime::Message::Ptr & aMsg,bool lastSignState,bool lastEncryptState,Composer::TemplateContext context,uint id,const QString & textSelection,const QString & customTemplate)228 KMComposerWin::KMComposerWin(const KMime::Message::Ptr &aMsg,
229                              bool lastSignState,
230                              bool lastEncryptState,
231                              Composer::TemplateContext context,
232                              uint id,
233                              const QString &textSelection,
234                              const QString &customTemplate)
235     : KMail::Composer(QStringLiteral("kmail-composer#"))
236     , mTextSelection(textSelection)
237     , mCustomTemplate(customTemplate)
238     , mFolder(Akonadi::Collection(-1))
239     , mId(id)
240     , mContext(context)
241     , mTooMyRecipientWarning(new TooManyRecipientsWarning(this))
242     , mIncorrectIdentityFolderWarning(new IncorrectIdentityFolderWarning(this))
243     , mPluginEditorManagerInterface(new KMailPluginEditorManagerInterface(this))
244     , mPluginEditorGrammarManagerInterface(new KMailPluginGrammarEditorManagerInterface(this))
245 
246 {
247     mGlobalAction = new KMComposerGlobalAction(this, this);
248     mComposerBase = new MessageComposer::ComposerViewBase(this, this);
249     mComposerBase->setIdentityManager(kmkernel->identityManager());
250 
251     connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::message, this, &KMComposerWin::slotMessage);
252     connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::insertText, this, &KMComposerWin::slotEditorPluginInsertText);
253     mPluginEditorCheckBeforeSendManagerInterface = new KMailPluginEditorCheckBeforeSendManagerInterface(this);
254     mPluginEditorInitManagerInterface = new KMailPluginEditorInitManagerInterface(this);
255     mPluginEditorConvertTextManagerInterface = new KMailPluginEditorConvertTextManagerInterface(this);
256 
257 
258     connect(mComposerBase, &MessageComposer::ComposerViewBase::disableHtml, this, &KMComposerWin::disableHtml);
259     connect(mComposerBase, &MessageComposer::ComposerViewBase::enableHtml, this, &KMComposerWin::enableHtml);
260     connect(mComposerBase, &MessageComposer::ComposerViewBase::failed, this, &KMComposerWin::slotSendFailed);
261     connect(mComposerBase, &MessageComposer::ComposerViewBase::sentSuccessfully, this, &KMComposerWin::slotSendSuccessful);
262     connect(mComposerBase, &MessageComposer::ComposerViewBase::modified, this, &KMComposerWin::setModified);
263 
264     (void)new MailcomposerAdaptor(this);
265     mdbusObjectPath = QLatin1String("/Composer_") + QString::number(++s_composerNumber);
266     QDBusConnection::sessionBus().registerObject(mdbusObjectPath, this);
267 
268     auto sigController = new MessageComposer::SignatureController(this);
269     connect(sigController, &MessageComposer::SignatureController::enableHtml, this, &KMComposerWin::enableHtml);
270     mComposerBase->setSignatureController(sigController);
271 
272     if (!kmkernel->xmlGuiInstanceName().isEmpty()) {
273         setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2"));
274     }
275     mMainWidget = new QWidget(this);
276     // splitter between the headers area and the actual editor
277     mHeadersToEditorSplitter = new QSplitter(Qt::Vertical, mMainWidget);
278     mHeadersToEditorSplitter->setObjectName(QStringLiteral("mHeadersToEditorSplitter"));
279     mHeadersToEditorSplitter->setChildrenCollapsible(false);
280     mHeadersArea = new QWidget(mHeadersToEditorSplitter);
281     mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding);
282     mHeadersToEditorSplitter->addWidget(mHeadersArea);
283     const QList<int> defaultSizes{0};
284     mHeadersToEditorSplitter->setSizes(defaultSizes);
285 
286     auto v = new QVBoxLayout(mMainWidget);
287     v->setContentsMargins({});
288     v->addWidget(mHeadersToEditorSplitter);
289     auto identity = new KIdentityManagement::IdentityCombo(kmkernel->identityManager(), mHeadersArea);
290     identity->setCurrentIdentity(mId);
291     identity->setObjectName(QStringLiteral("identitycombo"));
292     connect(identity, &KIdentityManagement::IdentityCombo::identityDeleted, this, &KMComposerWin::slotIdentityDeleted);
293     connect(identity, &KIdentityManagement::IdentityCombo::invalidIdentity, this, &KMComposerWin::slotInvalidIdentity);
294     mComposerBase->setIdentityCombo(identity);
295 
296     sigController->setIdentityCombo(identity);
297     sigController->suspend(); // we have to do identity change tracking ourselves due to the template code
298 
299     auto dictionaryCombo = new DictionaryComboBox(mHeadersArea);
300     dictionaryCombo->setToolTip(i18n("Select the dictionary to use when spell-checking this message"));
301     mComposerBase->setDictionary(dictionaryCombo);
302 
303     mFccFolder = new MailCommon::FolderRequester(mHeadersArea);
304     mFccFolder->setNotAllowToCreateNewFolder(true);
305     mFccFolder->setMustBeReadWrite(true);
306 
307     mFccFolder->setToolTip(i18n("Select the sent-mail folder where a copy of this message will be saved"));
308     connect(mFccFolder, &MailCommon::FolderRequester::folderChanged, this, &KMComposerWin::slotFccFolderChanged);
309     connect(mFccFolder, &MailCommon::FolderRequester::invalidFolder, this, &KMComposerWin::slotFccIsInvalid);
310 
311     auto transport = new MailTransport::TransportComboBox(mHeadersArea);
312     transport->setToolTip(i18n("Select the outgoing account to use for sending this message"));
313     mComposerBase->setTransportCombo(transport);
314     connect(transport, &MailTransport::TransportComboBox::activated, this, &KMComposerWin::slotTransportChanged);
315     connect(transport, &MailTransport::TransportComboBox::transportRemoved, this, &KMComposerWin::slotTransportRemoved);
316     mEdtFrom = new MessageComposer::ComposerLineEdit(false, mHeadersArea);
317     mEdtFrom->installEventFilter(this);
318     mEdtFrom->setObjectName(QStringLiteral("fromLine"));
319     mEdtFrom->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
320     mEdtFrom->setToolTip(i18n("Set the \"From:\" email address for this message"));
321 
322     auto recipientsEditor = new MessageComposer::RecipientsEditor(mHeadersArea);
323     recipientsEditor->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
324     connect(recipientsEditor, &MessageComposer::RecipientsEditor::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
325     connect(recipientsEditor, &MessageComposer::RecipientsEditor::sizeHintChanged, this, &KMComposerWin::recipientEditorSizeHintChanged);
326     connect(recipientsEditor, &MessageComposer::RecipientsEditor::lineAdded, this, &KMComposerWin::slotRecipientEditorLineAdded);
327     connect(recipientsEditor, &MessageComposer::RecipientsEditor::focusInRecipientLineEdit, this, &KMComposerWin::slotRecipientEditorLineFocused);
328     mComposerBase->setRecipientsEditor(recipientsEditor);
329 
330     mEdtSubject = new PimCommon::LineEditWithAutoCorrection(mHeadersArea, QStringLiteral("kmail2rc"));
331     mEdtSubject->installEventFilter(this);
332     mEdtSubject->setActivateLanguageMenu(false);
333     mEdtSubject->setToolTip(i18n("Set a subject for this message"));
334     mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection());
335     mLblIdentity = new QLabel(i18n("&Identity:"), mHeadersArea);
336     mDictionaryLabel = new QLabel(i18n("&Dictionary:"), mHeadersArea);
337     mLblFcc = new QLabel(i18n("&Sent-Mail folder:"), mHeadersArea);
338     mLblTransport = new QLabel(i18n("&Mail transport:"), mHeadersArea);
339     mLblFrom = new QLabel(i18nc("sender address field", "&From:"), mHeadersArea);
340     mLblSubject = new QLabel(i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea);
341     mShowHeaders = KMailSettings::self()->headers();
342     mDone = false;
343     // the attachment view is separated from the editor by a splitter
344     mSplitter = new QSplitter(Qt::Vertical, mMainWidget);
345     mSplitter->setObjectName(QStringLiteral("mSplitter"));
346     mSplitter->setChildrenCollapsible(false);
347     mSnippetSplitter = new QSplitter(Qt::Horizontal, mSplitter);
348     mSnippetSplitter->setObjectName(QStringLiteral("mSnippetSplitter"));
349     mSplitter->addWidget(mSnippetSplitter);
350 
351     auto editorAndCryptoStateIndicators = new QWidget(mSplitter);
352     mCryptoStateIndicatorWidget = new CryptoStateIndicatorWidget(this);
353     mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator());
354 
355     auto vbox = new QVBoxLayout(editorAndCryptoStateIndicators);
356     vbox->setContentsMargins({});
357 
358     mPotentialPhishingEmailWarning = new PotentialPhishingEmailWarning(this);
359     connect(mPotentialPhishingEmailWarning, &PotentialPhishingEmailWarning::sendNow, this, &KMComposerWin::slotCheckSendNowStep2);
360     vbox->addWidget(mPotentialPhishingEmailWarning);
361 
362     mAttachmentMissing = new AttachmentMissingWarning(this);
363     connect(mAttachmentMissing, &AttachmentMissingWarning::attachMissingFile, this, &KMComposerWin::slotAttachMissingFile);
364     connect(mAttachmentMissing, &AttachmentMissingWarning::explicitClosedMissingAttachment, this, &KMComposerWin::slotExplicitClosedMissingAttachment);
365     vbox->addWidget(mAttachmentMissing);
366 
367     auto composerEditorNg = new KMComposerEditorNg(this, mCryptoStateIndicatorWidget);
368     mRichTextEditorwidget = new KPIMTextEdit::RichTextEditorWidget(composerEditorNg, mCryptoStateIndicatorWidget);
369     composerEditorNg->installEventFilter(this);
370 
371     connect(composerEditorNg, &KMComposerEditorNg::insertEmoticon, mGlobalAction, &KMComposerGlobalAction::slotInsertEmoticon);
372     // Don't use new connect api here. It crashes
373     connect(composerEditorNg, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged()));
374     connect(composerEditorNg, &KMComposerEditorNg::selectionChanged, this, &KMComposerWin::slotSelectionChanged);
375     // connect(editor, &KMComposerEditor::textChanged, this, &KMComposeWin::slotEditorTextChanged);
376     mComposerBase->setEditor(composerEditorNg);
377 
378     vbox->addWidget(mIncorrectIdentityFolderWarning);
379 
380     mAttachmentFromExternalMissing = new AttachmentAddedFromExternalWarning(this);
381     vbox->addWidget(mAttachmentFromExternalMissing);
382     vbox->addWidget(mTooMyRecipientWarning);
383 
384     vbox->addWidget(mCryptoStateIndicatorWidget);
385     vbox->addWidget(mRichTextEditorwidget);
386 
387     mSnippetSplitter->insertWidget(0, editorAndCryptoStateIndicators);
388     mSnippetSplitter->setOpaqueResize(true);
389     sigController->setEditor(composerEditorNg);
390 
391     mHeadersToEditorSplitter->addWidget(mSplitter);
392     composerEditorNg->setAcceptDrops(true);
393     connect(sigController,
394             &MessageComposer::SignatureController::signatureAdded,
395             mComposerBase->editor()->externalComposer(),
396             &KPIMTextEdit::RichTextExternalComposer::startExternalEditor);
397 
398     connect(dictionaryCombo, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KMComposerWin::slotSpellCheckingLanguage);
399 
400     connect(composerEditorNg, &KMComposerEditorNg::languageChanged, this, &KMComposerWin::slotDictionaryLanguageChanged);
401     connect(composerEditorNg, &KMComposerEditorNg::spellCheckStatus, this, &KMComposerWin::slotSpellCheckingStatus);
402     connect(composerEditorNg, &KMComposerEditorNg::insertModeChanged, this, &KMComposerWin::slotOverwriteModeChanged);
403     connect(composerEditorNg, &KMComposerEditorNg::spellCheckingFinished, this, &KMComposerWin::slotDelayedCheckSendNow);
404     mSnippetWidget = new MailCommon::SnippetTreeView(actionCollection(), mSnippetSplitter);
405     connect(mSnippetWidget, &MailCommon::SnippetTreeView::insertSnippetInfo, this, &KMComposerWin::insertSnippetInfo);
406     connect(composerEditorNg, &KMComposerEditorNg::insertSnippet, mSnippetWidget->snippetsManager(), &MailCommon::SnippetsManager::insertSnippet);
407     mSnippetWidget->setVisible(KMailSettings::self()->showSnippetManager());
408     mSnippetSplitter->addWidget(mSnippetWidget);
409     mSnippetSplitter->setCollapsible(0, false);
410     mSnippetSplitterCollapser = new KSplitterCollapserButton(mSnippetWidget, mSnippetSplitter);
411     mSnippetSplitterCollapser->setVisible(KMailSettings::self()->showSnippetManager());
412 
413     mSplitter->setOpaqueResize(true);
414 
415     setWindowTitle(i18nc("@title:window", "Composer"));
416     setMinimumSize(200, 200);
417 
418     mCustomToolsWidget = new PimCommon::CustomToolsWidgetNg(this);
419     mCustomToolsWidget->initializeView(actionCollection(), PimCommon::CustomToolsPluginManager::self()->pluginsList());
420     mSplitter->addWidget(mCustomToolsWidget);
421     connect(mCustomToolsWidget, &PimCommon::CustomToolsWidgetNg::insertText, this, &KMComposerWin::slotInsertShortUrl);
422 
423     auto attachmentModel = new MessageComposer::AttachmentModel(this);
424     auto attachmentView = new KMail::AttachmentView(attachmentModel, mSplitter);
425     attachmentView->hideIfEmpty();
426     connect(attachmentView, &KMail::AttachmentView::modified, this, &KMComposerWin::setModified);
427     auto attachmentController = new KMail::AttachmentController(attachmentModel, attachmentView, this);
428 
429     mComposerBase->setAttachmentModel(attachmentModel);
430     mComposerBase->setAttachmentController(attachmentController);
431 
432     if (KMailSettings::self()->showForgottenAttachmentWarning()) {
433         mVerifyMissingAttachment = new QTimer(this);
434         mVerifyMissingAttachment->setSingleShot(true);
435         mVerifyMissingAttachment->setInterval(1000 * 5);
436         connect(mVerifyMissingAttachment, &QTimer::timeout, this, &KMComposerWin::slotVerifyMissingAttachmentTimeout);
437     }
438     connect(attachmentController, &KMail::AttachmentController::fileAttached, mAttachmentMissing, &AttachmentMissingWarning::slotFileAttached);
439 
440     mExternalEditorWarning = new ExternalEditorWarning(this);
441     v->addWidget(mExternalEditorWarning);
442 
443     mPluginEditorManagerInterface->setParentWidget(this);
444     mPluginEditorManagerInterface->setRichTextEditor(mRichTextEditorwidget->editor());
445     mPluginEditorManagerInterface->setActionCollection(actionCollection());
446     mPluginEditorManagerInterface->setComposerInterface(mComposerBase);
447 
448     mPluginEditorCheckBeforeSendManagerInterface->setParentWidget(this);
449 
450     mPluginEditorInitManagerInterface->setParentWidget(this);
451     mPluginEditorInitManagerInterface->setRichTextEditor(composerEditorNg);
452 
453     mPluginEditorConvertTextManagerInterface->setParentWidget(this);
454     mPluginEditorConvertTextManagerInterface->setActionCollection(actionCollection());
455     mPluginEditorConvertTextManagerInterface->setRichTextEditor(composerEditorNg);
456 
457     mPluginEditorGrammarManagerInterface->setParentWidget(this);
458     mPluginEditorGrammarManagerInterface->setActionCollection(actionCollection());
459     mPluginEditorGrammarManagerInterface->setRichTextEditor(composerEditorNg);
460     mPluginEditorGrammarManagerInterface->setCustomToolsWidget(mCustomToolsWidget);
461 
462     setupStatusBar(attachmentView->widget());
463     setupActions();
464     setupEditor();
465     rethinkFields();
466     readConfig();
467 
468     updateSignatureAndEncryptionStateIndicators();
469 
470     applyMainWindowSettings(KMKernel::self()->config()->group("Composer"));
471 
472     mUpdateWindowTitleConnection = connect(mEdtSubject, &PimCommon::LineEditWithAutoCorrection::textChanged, this, &KMComposerWin::slotUpdateWindowTitle);
473     mIdentityConnection = connect(identity, &KIdentityManagement::IdentityCombo::identityChanged, this, [this](uint val) {
474         slotIdentityChanged(val);
475     });
476     connect(kmkernel->identityManager(), qOverload<uint>(&KIdentityManagement::IdentityManager::changed), this, [this](uint val) {
477         if (currentIdentity() == val) {
478             slotIdentityChanged(val);
479         }
480     });
481 
482     connect(mEdtFrom, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
483     connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::collectionRemoved, this, &KMComposerWin::slotFolderRemoved);
484     connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::slotConfigChanged);
485 
486     mMainWidget->resize(800, 600);
487     setCentralWidget(mMainWidget);
488 
489     if (KMailSettings::self()->useHtmlMarkup()) {
490         enableHtml();
491     } else {
492         disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
493     }
494 
495     if (KMailSettings::self()->useExternalEditor()) {
496         composerEditorNg->setUseExternalEditor(true);
497         composerEditorNg->setExternalEditorPath(KMailSettings::self()->externalEditor());
498     }
499 
500     const QList<KPIM::MultiplyingLine *> lstLines = recipientsEditor->lines();
501     for (KPIM::MultiplyingLine *line : lstLines) {
502         slotRecipientEditorLineAdded(line);
503     }
504 
505     if (aMsg) {
506         setMessage(aMsg, lastSignState, lastEncryptState);
507     }
508 
509     mComposerBase->recipientsEditor()->setFocusBottom();
510     composerEditorNg->composerActions()->updateActionStates(); // set toolbar buttons to correct values
511 
512     mDone = true;
513 
514     mDummyComposer = new MessageComposer::Composer(this);
515     mDummyComposer->globalPart()->setParentWidgetForGui(this);
516 
517     KConfigGroup grp(KMKernel::self()->config()->group("Composer"));
518     setAutoSaveSettings(grp, true);
519     connect(mComposerBase, &MessageComposer::ComposerViewBase::tooManyRecipient, this, &KMComposerWin::slotTooManyRecipients);
520 }
521 
~KMComposerWin()522 KMComposerWin::~KMComposerWin()
523 {
524     disconnect(mUpdateWindowTitleConnection);
525     // When we have a collection set, store the message back to that collection.
526     // Note that when we save the message or sent it, mFolder is set back to 0.
527     // So this for example kicks in when opening a draft and then closing the window.
528     if (mFolder.isValid() && mMsg && isModified()) {
529         auto saveDraftJob = new SaveDraftJob(mMsg, mFolder);
530         saveDraftJob->start();
531     }
532 
533     delete mComposerBase;
534 }
535 
slotTooManyRecipients(bool b)536 void KMComposerWin::slotTooManyRecipients(bool b)
537 {
538     if (b) {
539         mTooMyRecipientWarning->animatedShow();
540     } else {
541         mTooMyRecipientWarning->animatedHide();
542     }
543 }
544 
slotRecipientEditorLineFocused()545 void KMComposerWin::slotRecipientEditorLineFocused()
546 {
547     mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields);
548 }
549 
modeType() const550 KMComposerWin::ModeType KMComposerWin::modeType() const
551 {
552     return mModeType;
553 }
554 
setModeType(const ModeType & modeType)555 void KMComposerWin::setModeType(const ModeType &modeType)
556 {
557     mModeType = modeType;
558 }
559 
eventFilter(QObject * obj,QEvent * event)560 bool KMComposerWin::eventFilter(QObject *obj, QEvent *event)
561 {
562     if (event->type() == QEvent::FocusIn) {
563         if (obj == mEdtSubject) {
564             mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::SubjectField);
565         } else if (obj == mComposerBase->recipientsEditor()) {
566             mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields);
567         } else if (obj == mEdtFrom) {
568             mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields);
569         }
570     }
571     return KMail::Composer::eventFilter(obj, event);
572 }
573 
insertSnippetInfo(const MailCommon::SnippetInfo & info)574 void KMComposerWin::insertSnippetInfo(const MailCommon::SnippetInfo &info)
575 {
576     {
577         if (!info.to.isEmpty()) {
578             const QStringList lst = KEmailAddress::splitAddressList(info.to);
579             for (const QString &addr : lst) {
580                 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::To)) {
581                     qCWarning(KMAIL_LOG) << "Impossible to add to entry";
582                 }
583             }
584         }
585     }
586     {
587         if (!info.cc.isEmpty()) {
588             const QStringList lst = KEmailAddress::splitAddressList(info.cc);
589             for (const QString &addr : lst) {
590                 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::Cc)) {
591                     qCWarning(KMAIL_LOG) << "Impossible to add cc entry";
592                 }
593             }
594         }
595     }
596     {
597         if (!info.bcc.isEmpty()) {
598             const QStringList lst = KEmailAddress::splitAddressList(info.bcc);
599             for (const QString &addr : lst) {
600                 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
601                     qCWarning(KMAIL_LOG) << "Impossible to add bcc entry";
602                 }
603             }
604         }
605     }
606     {
607         if (!info.attachment.isEmpty()) {
608             const QStringList lst = info.attachment.split(QLatin1Char(','));
609             for (const QString &attach : lst) {
610                 auto job = new MessageComposer::ConvertSnippetVariablesJob(this);
611                 job->setText(attach);
612                 auto interface = new MessageComposer::ComposerViewInterface(mComposerBase);
613                 job->setComposerViewInterface(interface);
614                 connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) {
615                     if (!str.isEmpty()) {
616                         const QUrl localUrl = QUrl::fromLocalFile(str);
617                         AttachmentInfo info;
618                         info.url = localUrl;
619                         addAttachment(QVector<AttachmentInfo>() << info, false);
620                     }
621                 });
622                 job->start();
623             }
624         }
625     }
626     {
627         if (!info.subject.isEmpty()) {
628             // Convert subject
629             auto job = new MessageComposer::ConvertSnippetVariablesJob(this);
630             job->setText(info.subject);
631             auto interface = new MessageComposer::ComposerViewInterface(mComposerBase);
632             job->setComposerViewInterface(interface);
633             connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) {
634                 if (!str.isEmpty()) {
635                     if (mComposerBase->subject().isEmpty()) { // Add subject only if we don't have subject
636                         mEdtSubject->setText(str);
637                     }
638                 }
639             });
640             job->start();
641         }
642     }
643     {
644         if (!info.text.isEmpty()) {
645             // Convert plain text
646             auto job = new MessageComposer::ConvertSnippetVariablesJob(this);
647             job->setText(info.text);
648             auto interface = new MessageComposer::ComposerViewInterface(mComposerBase);
649             job->setComposerViewInterface(interface);
650             connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) {
651                 mComposerBase->editor()->insertPlainText(str);
652             });
653             job->start();
654         }
655     }
656 }
657 
slotSpellCheckingLanguage(const QString & language)658 void KMComposerWin::slotSpellCheckingLanguage(const QString &language)
659 {
660     mComposerBase->editor()->setSpellCheckingLanguage(language);
661     mEdtSubject->setSpellCheckingLanguage(language);
662 }
663 
dbusObjectPath() const664 QString KMComposerWin::dbusObjectPath() const
665 {
666     return mdbusObjectPath;
667 }
668 
slotEditorTextChanged()669 void KMComposerWin::slotEditorTextChanged()
670 {
671     const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty();
672     mFindText->setEnabled(textIsNotEmpty);
673     mFindNextText->setEnabled(textIsNotEmpty);
674     mReplaceText->setEnabled(textIsNotEmpty);
675     mSelectAll->setEnabled(textIsNotEmpty);
676     if (mVerifyMissingAttachment && !mVerifyMissingAttachment->isActive()) {
677         mVerifyMissingAttachment->start();
678     }
679 }
680 
send(int how)681 void KMComposerWin::send(int how)
682 {
683     switch (how) {
684     case 1:
685         slotSendNow();
686         break;
687     default:
688     case 0:
689     // TODO: find out, what the default send method is and send it this way
690     case 2:
691         slotSendLater();
692         break;
693     }
694 }
695 
addAttachmentsAndSend(const QList<QUrl> & urls,const QString & comment,int how)696 void KMComposerWin::addAttachmentsAndSend(const QList<QUrl> &urls, const QString &comment, int how)
697 {
698     const int nbUrl = urls.count();
699     for (int i = 0; i < nbUrl; ++i) {
700         mComposerBase->addAttachment(urls.at(i), comment, true);
701     }
702 
703     send(how);
704 }
705 
addAttachment(const QVector<KMail::Composer::AttachmentInfo> & infos,bool showWarning)706 void KMComposerWin::addAttachment(const QVector<KMail::Composer::AttachmentInfo> &infos, bool showWarning)
707 {
708     QStringList lst;
709     for (const AttachmentInfo &info : infos) {
710         if (showWarning) {
711             lst.append(info.url.toDisplayString());
712         }
713         mComposerBase->addAttachment(info.url, info.comment, false);
714     }
715     if (showWarning) {
716         mAttachmentFromExternalMissing->setAttachmentNames(lst);
717         mAttachmentFromExternalMissing->animatedShow();
718     }
719 }
720 
addAttachment(const QString & name,KMime::Headers::contentEncoding cte,const QString & charset,const QByteArray & data,const QByteArray & mimeType)721 void KMComposerWin::addAttachment(const QString &name,
722                                   KMime::Headers::contentEncoding cte,
723                                   const QString &charset,
724                                   const QByteArray &data,
725                                   const QByteArray &mimeType)
726 {
727     Q_UNUSED(cte)
728     mComposerBase->addAttachment(name, name, charset, data, mimeType);
729 }
730 
readConfig(bool reload)731 void KMComposerWin::readConfig(bool reload)
732 {
733     mEdtFrom->setCompletionMode(static_cast<KCompletion::CompletionMode>(KMailSettings::self()->completionMode()));
734     mComposerBase->recipientsEditor()->setCompletionMode(static_cast<KCompletion::CompletionMode>(KMailSettings::self()->completionMode()));
735 
736     if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
737         mBodyFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
738         mFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
739     } else {
740         mBodyFont = KMailSettings::self()->composerFont();
741         mFixedFont = MessageViewer::MessageViewerSettings::self()->fixedFont();
742     }
743 
744     slotUpdateFont();
745     mEdtFrom->setFont(mBodyFont);
746     mEdtSubject->setFont(mBodyFont);
747 
748     if (!reload) {
749         QSize composerSize = KMailSettings::self()->composerSize();
750         if (composerSize.width() < 200) {
751             composerSize.setWidth(200);
752         }
753         if (composerSize.height() < 200) {
754             composerSize.setHeight(200);
755         }
756         resize(composerSize);
757 
758         if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) {
759             mSnippetSplitter->setSizes(KMailSettings::self()->snippetSplitterPosition());
760         } else {
761             const QList<int> defaults{(int)(width() * 0.8), (int)(width() * 0.2)};
762             mSnippetSplitter->setSizes(defaults);
763         }
764     }
765 
766     mComposerBase->identityCombo()->setCurrentIdentity(mId);
767     qCDebug(KMAIL_LOG) << mComposerBase->identityCombo()->currentIdentityName();
768     const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(mId);
769 
770     mComposerBase->setAutoSaveInterval(KMailSettings::self()->autosaveInterval() * 1000 * 60);
771 
772     mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
773 
774     const QString fccName = ident.fcc();
775     setFcc(fccName);
776 }
777 
writeConfig()778 void KMComposerWin::writeConfig()
779 {
780     KMailSettings::self()->setHeaders(mShowHeaders);
781     KMailSettings::self()->setCurrentTransport(mComposerBase->transportComboBox()->currentText());
782     KMailSettings::self()->setPreviousIdentity(currentIdentity());
783     KMailSettings::self()->setPreviousFcc(QString::number(mFccFolder->collection().id()));
784     KMailSettings::self()->setPreviousDictionary(mComposerBase->dictionary()->currentDictionaryName());
785     KMailSettings::self()->setAutoSpellChecking(mAutoSpellCheckingAction->isChecked());
786     MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mFixedFontAction->isChecked());
787     if (!mForceDisableHtml) {
788         KMailSettings::self()->setUseHtmlMarkup(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
789     }
790     KMailSettings::self()->setComposerSize(size());
791     KMailSettings::self()->setShowSnippetManager(mSnippetAction->isChecked());
792 
793     if (mSnippetAction->isChecked()) {
794         KMailSettings::setSnippetSplitterPosition(mSnippetSplitter->sizes());
795     }
796 
797     // make sure config changes are written to disk, cf. bug 127538
798     KMKernel::self()->slotSyncConfig();
799 }
800 
createSimpleComposer()801 MessageComposer::Composer *KMComposerWin::createSimpleComposer()
802 {
803     QVector<QByteArray> charsets = mCodecAction->mimeCharsets();
804     if (!mOriginalPreferredCharset.isEmpty()) {
805         charsets.insert(0, mOriginalPreferredCharset);
806     }
807     mComposerBase->setFrom(from());
808     mComposerBase->setSubject(subject());
809     mComposerBase->setCharsets(charsets);
810     MessageComposer::Composer *composer = new MessageComposer::Composer();
811     mComposerBase->fillComposer(composer);
812     return composer;
813 }
814 
canSignEncryptAttachments() const815 bool KMComposerWin::canSignEncryptAttachments() const
816 {
817     return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat;
818 }
819 
slotUpdateView()820 void KMComposerWin::slotUpdateView()
821 {
822     if (!mDone) {
823         return; // otherwise called from rethinkFields during the construction
824         // which is not the intended behavior
825     }
826 
827     // This sucks awfully, but no, I cannot get an activated(int id) from
828     // actionContainer()
829     auto *act = ::qobject_cast<KToggleAction *>(sender());
830     if (!act) {
831         return;
832     }
833     int id;
834 
835     if (act == mAllFieldsAction) {
836         id = 0;
837     } else if (act == mIdentityAction) {
838         id = HDR_IDENTITY;
839     } else if (act == mTransportAction) {
840         id = HDR_TRANSPORT;
841     } else if (act == mFromAction) {
842         id = HDR_FROM;
843     } else if (act == mSubjectAction) {
844         id = HDR_SUBJECT;
845     } else if (act == mFccAction) {
846         id = HDR_FCC;
847     } else if (act == mDictionaryAction) {
848         id = HDR_DICTIONARY;
849     } else {
850         qCDebug(KMAIL_LOG) << "Something is wrong (Oh, yeah?)";
851         return;
852     }
853 
854     bool forceAllHeaders = false;
855     // sanders There's a bug here this logic doesn't work if no
856     // fields are shown and then show all fields is selected.
857     // Instead of all fields being shown none are.
858     if (!act->isChecked()) {
859         // hide header
860         if (id > 0) {
861             mShowHeaders = mShowHeaders & ~id;
862         } else {
863             mShowHeaders = std::abs(mShowHeaders);
864         }
865     } else {
866         // show header
867         if (id > 0) {
868             mShowHeaders |= id;
869         } else {
870             mShowHeaders = -std::abs(mShowHeaders);
871             if (mShowHeaders == 0) {
872                 forceAllHeaders = true;
873             }
874         }
875     }
876     rethinkFields(true, forceAllHeaders);
877 }
878 
calcColumnWidth(int which,long allShowing,int width) const879 int KMComposerWin::calcColumnWidth(int which, long allShowing, int width) const
880 {
881     if ((allShowing & which) == 0) {
882         return width;
883     }
884 
885     QLabel *w = nullptr;
886     if (which == HDR_IDENTITY) {
887         w = mLblIdentity;
888     } else if (which == HDR_DICTIONARY) {
889         w = mDictionaryLabel;
890     } else if (which == HDR_FCC) {
891         w = mLblFcc;
892     } else if (which == HDR_TRANSPORT) {
893         w = mLblTransport;
894     } else if (which == HDR_FROM) {
895         w = mLblFrom;
896     } else if (which == HDR_SUBJECT) {
897         w = mLblSubject;
898     } else {
899         return width;
900     }
901 
902     w->setBuddy(mComposerBase->editor()); // set dummy so we don't calculate width of '&' for this label.
903     w->adjustSize();
904     w->show();
905     return qMax(width, w->sizeHint().width());
906 }
907 
rethinkFields(bool fromSlot,bool forceAllHeaders)908 void KMComposerWin::rethinkFields(bool fromSlot, bool forceAllHeaders)
909 {
910     // This sucks even more but again no ids. sorry (sven)
911     int mask;
912     int row;
913     long showHeaders;
914 
915     if ((mShowHeaders < 0) || forceAllHeaders) {
916         showHeaders = HDR_ALL;
917     } else {
918         showHeaders = mShowHeaders;
919     }
920 
921     for (mask = 1, mNumHeaders = 0; mask <= showHeaders; mask <<= 1) {
922         if ((showHeaders & mask) != 0) {
923             ++mNumHeaders;
924         }
925     }
926     delete mGrid;
927     mGrid = new QGridLayout(mHeadersArea);
928     mGrid->setColumnStretch(0, 1);
929     mGrid->setColumnStretch(1, 100);
930     mGrid->setRowStretch(mNumHeaders + 1, 100);
931 
932     row = 0;
933 
934     mLabelWidth = mComposerBase->recipientsEditor()->setFirstColumnWidth(0) + 2;
935     if (std::abs(showHeaders) & HDR_IDENTITY) {
936         mLabelWidth = calcColumnWidth(HDR_IDENTITY, showHeaders, mLabelWidth);
937     }
938     if (std::abs(showHeaders) & HDR_DICTIONARY) {
939         mLabelWidth = calcColumnWidth(HDR_DICTIONARY, showHeaders, mLabelWidth);
940     }
941     if (std::abs(showHeaders) & HDR_FCC) {
942         mLabelWidth = calcColumnWidth(HDR_FCC, showHeaders, mLabelWidth);
943     }
944     if (std::abs(showHeaders) & HDR_TRANSPORT) {
945         mLabelWidth = calcColumnWidth(HDR_TRANSPORT, showHeaders, mLabelWidth);
946     }
947     if (std::abs(showHeaders) & HDR_FROM) {
948         mLabelWidth = calcColumnWidth(HDR_FROM, showHeaders, mLabelWidth);
949     }
950     if (std::abs(showHeaders) & HDR_SUBJECT) {
951         mLabelWidth = calcColumnWidth(HDR_SUBJECT, showHeaders, mLabelWidth);
952     }
953 
954     if (!fromSlot) {
955         mAllFieldsAction->setChecked(showHeaders == HDR_ALL);
956     }
957 
958     if (!fromSlot) {
959         mIdentityAction->setChecked(std::abs(mShowHeaders) & HDR_IDENTITY);
960     }
961     rethinkHeaderLine(showHeaders, HDR_IDENTITY, row, mLblIdentity, mComposerBase->identityCombo());
962 
963     if (!fromSlot) {
964         mDictionaryAction->setChecked(std::abs(mShowHeaders) & HDR_DICTIONARY);
965     }
966     rethinkHeaderLine(showHeaders, HDR_DICTIONARY, row, mDictionaryLabel, mComposerBase->dictionary());
967 
968     if (!fromSlot) {
969         mFccAction->setChecked(std::abs(mShowHeaders) & HDR_FCC);
970     }
971     rethinkHeaderLine(showHeaders, HDR_FCC, row, mLblFcc, mFccFolder);
972 
973     if (!fromSlot) {
974         mTransportAction->setChecked(std::abs(mShowHeaders) & HDR_TRANSPORT);
975     }
976     rethinkHeaderLine(showHeaders, HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox());
977 
978     if (!fromSlot) {
979         mFromAction->setChecked(std::abs(mShowHeaders) & HDR_FROM);
980     }
981     rethinkHeaderLine(showHeaders, HDR_FROM, row, mLblFrom, mEdtFrom);
982 
983     mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2);
984     ++row;
985     connect(mEdtFrom, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop);
986     connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtFrom, qOverload<>(&QWidget::setFocus));
987 
988     connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusDown, mEdtSubject, qOverload<>(&QWidget::setFocus));
989     connect(mEdtSubject, &PimCommon::SpellCheckLineEdit::focusUp, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusBottom);
990 
991     mComposerBase->recipientsEditor();
992 
993     if (!fromSlot) {
994         mSubjectAction->setChecked(std::abs(mShowHeaders) & HDR_SUBJECT);
995     }
996     rethinkHeaderLine(showHeaders, HDR_SUBJECT, row, mLblSubject, mEdtSubject);
997     connectFocusMoving(mEdtSubject, mComposerBase->editor());
998 
999     assert(row <= mNumHeaders + 1);
1000 
1001     mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
1002 
1003     mIdentityAction->setEnabled(!mAllFieldsAction->isChecked());
1004     mDictionaryAction->setEnabled(!mAllFieldsAction->isChecked());
1005     mTransportAction->setEnabled(!mAllFieldsAction->isChecked());
1006     mFromAction->setEnabled(!mAllFieldsAction->isChecked());
1007     mFccAction->setEnabled(!mAllFieldsAction->isChecked());
1008     mSubjectAction->setEnabled(!mAllFieldsAction->isChecked());
1009     mComposerBase->recipientsEditor()->setFirstColumnWidth(mLabelWidth);
1010 }
1011 
connectFocusMoving(QWidget * prev,QWidget * next)1012 QWidget *KMComposerWin::connectFocusMoving(QWidget *prev, QWidget *next)
1013 {
1014     connect(prev, SIGNAL(focusDown()), next, SLOT(setFocus()));
1015     connect(next, SIGNAL(focusUp()), prev, SLOT(setFocus()));
1016 
1017     return next;
1018 }
1019 
rethinkHeaderLine(int aValue,int aMask,int & aRow,QLabel * aLbl,QWidget * aCbx)1020 void KMComposerWin::rethinkHeaderLine(int aValue, int aMask, int &aRow, QLabel *aLbl, QWidget *aCbx)
1021 {
1022     if (aValue & aMask) {
1023         aLbl->setBuddy(aCbx);
1024         aLbl->setFixedWidth(mLabelWidth);
1025         mGrid->addWidget(aLbl, aRow, 0);
1026 
1027         mGrid->addWidget(aCbx, aRow, 1);
1028         aCbx->show();
1029         aLbl->show();
1030         aRow++;
1031     } else {
1032         aLbl->hide();
1033         aCbx->hide();
1034     }
1035 }
1036 
slotUpdateComposer(const KIdentityManagement::Identity & ident,const KMime::Message::Ptr & msg,uint uoid,uint uoldId,bool wasModified)1037 void KMComposerWin::slotUpdateComposer(const KIdentityManagement::Identity &ident, const KMime::Message::Ptr &msg, uint uoid, uint uoldId, bool wasModified)
1038 {
1039     mComposerBase->updateTemplate(msg);
1040     updateSignature(uoid, uoldId);
1041     updateComposerAfterIdentityChanged(ident, uoid, wasModified);
1042 }
1043 
applyTemplate(uint uoid,uint uOldId,const KIdentityManagement::Identity & ident,bool wasModified)1044 void KMComposerWin::applyTemplate(uint uoid, uint uOldId, const KIdentityManagement::Identity &ident, bool wasModified)
1045 {
1046     TemplateParser::TemplateParserJob::Mode mode;
1047     switch (mContext) {
1048     case New:
1049         mode = TemplateParser::TemplateParserJob::NewMessage;
1050         break;
1051     case Reply:
1052         mode = TemplateParser::TemplateParserJob::Reply;
1053         break;
1054     case ReplyToAll:
1055         mode = TemplateParser::TemplateParserJob::ReplyAll;
1056         break;
1057     case Forward:
1058         mode = TemplateParser::TemplateParserJob::Forward;
1059         break;
1060     case NoTemplate:
1061         updateComposerAfterIdentityChanged(ident, uoid, wasModified);
1062         return;
1063     }
1064 
1065     auto header = new KMime::Headers::Generic("X-KMail-Templates");
1066     header->fromUnicodeString(ident.templates(), "utf-8");
1067     mMsg->setHeader(header);
1068 
1069     if (mode == TemplateParser::TemplateParserJob::NewMessage) {
1070         auto job = new KMComposerUpdateTemplateJob;
1071         connect(job, &KMComposerUpdateTemplateJob::updateComposer, this, &KMComposerWin::slotUpdateComposer);
1072         job->setMsg(mMsg);
1073         job->setCustomTemplate(mCustomTemplate);
1074         job->setTextSelection(mTextSelection);
1075         job->setWasModified(wasModified);
1076         job->setUoldId(uOldId);
1077         job->setUoid(uoid);
1078         job->setIdent(ident);
1079         job->setCollection(mCollectionForNewMessage);
1080         job->start();
1081     } else {
1082         if (auto hrd = mMsg->headerByType("X-KMail-Link-Message")) {
1083             Akonadi::Item::List items;
1084             const QStringList serNums = hrd->asUnicodeString().split(QLatin1Char(','));
1085             items.reserve(serNums.count());
1086             for (const QString &serNumStr : serNums) {
1087                 items << Akonadi::Item(serNumStr.toLongLong());
1088             }
1089 
1090             auto job = new Akonadi::ItemFetchJob(items, this);
1091             job->fetchScope().fetchFullPayload(true);
1092             job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1093             job->setProperty("mode", static_cast<int>(mode));
1094             job->setProperty("uoid", uoid);
1095             job->setProperty("uOldid", uOldId);
1096             connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDelayedApplyTemplate);
1097         }
1098         updateComposerAfterIdentityChanged(ident, uoid, wasModified);
1099     }
1100 }
1101 
slotDelayedApplyTemplate(KJob * job)1102 void KMComposerWin::slotDelayedApplyTemplate(KJob *job)
1103 {
1104     const Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
1105     // const Akonadi::Item::List items = fetchJob->items();
1106 
1107     // Readd ? const TemplateParser::TemplateParserJob::Mode mode = static_cast<TemplateParser::TemplateParserJob::Mode>(fetchJob->property("mode").toInt());
1108     const uint uoid = fetchJob->property("uoid").toUInt();
1109     const uint uOldId = fetchJob->property("uOldid").toUInt();
1110 #if 0 // FIXME template
1111     TemplateParser::TemplateParser parser(mMsg, mode);
1112     parser.setSelection(mTextSelection);
1113     parser.setAllowDecryption(true);
1114     parser.setWordWrap(MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
1115     parser.setIdentityManager(KMKernel::self()->identityManager());
1116     for (const Akonadi::Item &item : items) {
1117         if (!mCustomTemplate.isEmpty()) {
1118             parser.process(mCustomTemplate, MessageCore::Util::message(item));
1119         } else {
1120             parser.processWithIdentity(uoid, MessageCore::Util::message(item));
1121         }
1122     }
1123 #else
1124     mComposerBase->updateTemplate(mMsg);
1125     updateSignature(uoid, uOldId);
1126     qCWarning(KMAIL_LOG) << " void KMComposerWin::slotDelayedApplyTemplate(KJob *job) is not implemented after removing qtwebkit";
1127 #endif
1128 }
1129 
updateSignature(uint uoid,uint uOldId)1130 void KMComposerWin::updateSignature(uint uoid, uint uOldId)
1131 {
1132     const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid);
1133     const KIdentityManagement::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid(uOldId);
1134     mComposerBase->identityChanged(ident, oldIdentity, true);
1135 }
1136 
setCollectionForNewMessage(const Akonadi::Collection & folder)1137 void KMComposerWin::setCollectionForNewMessage(const Akonadi::Collection &folder)
1138 {
1139     mCollectionForNewMessage = folder;
1140 }
1141 
setQuotePrefix(uint uoid)1142 void KMComposerWin::setQuotePrefix(uint uoid)
1143 {
1144     QString quotePrefix;
1145     if (auto hrd = mMsg->headerByType("X-KMail-QuotePrefix")) {
1146         quotePrefix = hrd->asUnicodeString();
1147     }
1148     if (quotePrefix.isEmpty()) {
1149         // no quote prefix header, set quote prefix according in identity
1150         // TODO port templates to ComposerViewBase
1151 
1152         if (mCustomTemplate.isEmpty()) {
1153             const KIdentityManagement::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault(uoid);
1154             // Get quote prefix from template
1155             // ( custom templates don't specify custom quotes prefixes )
1156             TemplateParser::Templates quoteTemplate(TemplateParser::TemplatesConfiguration::configIdString(identity.uoid()));
1157             quotePrefix = quoteTemplate.quoteString();
1158         }
1159     }
1160     mComposerBase->editor()->setQuotePrefixName(MessageCore::StringUtil::formatQuotePrefix(quotePrefix, mMsg->from()->displayString()));
1161 }
1162 
setupActions()1163 void KMComposerWin::setupActions()
1164 {
1165     KActionMenuTransport *actActionNowMenu = nullptr;
1166     KActionMenuTransport *actActionLaterMenu = nullptr;
1167 
1168     if (MessageComposer::MessageComposerSettings::self()->sendImmediate()) {
1169         // default = send now, alternative = queue
1170         auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
1171         actionCollection()->addAction(QStringLiteral("send_default"), action);
1172         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return));
1173         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNowByShortcut);
1174 
1175         actActionNowMenu = new KActionMenuTransport(this);
1176         actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
1177         actActionNowMenu->setText(i18n("&Send Mail Via"));
1178 
1179         actActionNowMenu->setIconText(i18n("Send"));
1180         actionCollection()->addAction(QStringLiteral("send_default_via"), actActionNowMenu);
1181 
1182         action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this);
1183         actionCollection()->addAction(QStringLiteral("send_alternative"), action);
1184         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1185 
1186         actActionLaterMenu = new KActionMenuTransport(this);
1187         actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue")));
1188         actActionLaterMenu->setText(i18n("Send &Later Via"));
1189 
1190         actActionLaterMenu->setIconText(i18nc("Queue the message for sending at a later date", "Queue"));
1191         actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionLaterMenu);
1192     } else {
1193         // default = queue, alternative = send now
1194         auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this);
1195         actionCollection()->addAction(QStringLiteral("send_default"), action);
1196         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1197         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return));
1198 
1199         actActionLaterMenu = new KActionMenuTransport(this);
1200         actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue")));
1201         actActionLaterMenu->setText(i18n("Send &Later Via"));
1202         actionCollection()->addAction(QStringLiteral("send_default_via"), actActionLaterMenu);
1203 
1204         action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
1205         actionCollection()->addAction(QStringLiteral("send_alternative"), action);
1206         connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNow);
1207 
1208         actActionNowMenu = new KActionMenuTransport(this);
1209         actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
1210         actActionNowMenu->setText(i18n("&Send Mail Via"));
1211         actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionNowMenu);
1212     }
1213 
1214     connect(actActionNowMenu, &QAction::triggered, this, &KMComposerWin::slotSendNow);
1215     connect(actActionLaterMenu, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1216     connect(actActionNowMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendNowVia);
1217     connect(actActionLaterMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendLaterVia);
1218 
1219     auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this);
1220     actionCollection()->addAction(QStringLiteral("save_in_drafts"), action);
1221     KMail::Util::addQActionHelpText(action, i18n("Save email in Draft folder"));
1222     actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_S));
1223     connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveDraft);
1224 
1225     action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Template"), this);
1226     KMail::Util::addQActionHelpText(action, i18n("Save email in Template folder"));
1227     actionCollection()->addAction(QStringLiteral("save_in_templates"), action);
1228     connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveTemplate);
1229 
1230     action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this);
1231     KMail::Util::addQActionHelpText(action, i18n("Save email as text or html file"));
1232     actionCollection()->addAction(QStringLiteral("save_as_file"), action);
1233     connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveAsFile);
1234 
1235     action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this);
1236     actionCollection()->addAction(QStringLiteral("insert_file"), action);
1237     connect(action, &QAction::triggered, this, &KMComposerWin::slotInsertFile);
1238 
1239     mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this);
1240     actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction);
1241     connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &KMComposerWin::slotInsertRecentFile);
1242     connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &KMComposerWin::slotRecentListFileClear);
1243 
1244     const QStringList urls = KMailSettings::self()->recentUrls();
1245     for (const QString &url : urls) {
1246         mRecentAction->addUrl(QUrl(url));
1247     }
1248 
1249     action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this);
1250     KMail::Util::addQActionHelpText(action, i18n("Open Address Book"));
1251     actionCollection()->addAction(QStringLiteral("addressbook"), action);
1252     if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) {
1253         action->setEnabled(false);
1254     } else {
1255         connect(action, &QAction::triggered, this, &KMComposerWin::slotAddressBook);
1256     }
1257     action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Composer"), this);
1258     actionCollection()->addAction(QStringLiteral("new_composer"), action);
1259 
1260     connect(action, &QAction::triggered, this, &KMComposerWin::slotNewComposer);
1261     actionCollection()->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::New));
1262 
1263     action = new QAction(i18n("Select &Recipients..."), this);
1264     actionCollection()->addAction(QStringLiteral("select_recipients"), action);
1265     connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::selectRecipients);
1266     action = new QAction(i18n("Save &Distribution List..."), this);
1267     actionCollection()->addAction(QStringLiteral("save_distribution_list"), action);
1268     connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::saveDistributionList);
1269 
1270     KStandardAction::print(this, &KMComposerWin::slotPrint, actionCollection());
1271     KStandardAction::printPreview(this, &KMComposerWin::slotPrintPreview, actionCollection());
1272     KStandardAction::close(this, &KMComposerWin::slotClose, actionCollection());
1273 
1274     KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection());
1275     KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection());
1276     KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection());
1277     KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection());
1278     KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection());
1279     mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection());
1280 
1281     mFindText = KStandardAction::find(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFind, actionCollection());
1282     mFindNextText = KStandardAction::findNext(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFindNext, actionCollection());
1283 
1284     mReplaceText = KStandardAction::replace(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotReplace, actionCollection());
1285     actionCollection()->addAction(KStandardAction::Spelling, QStringLiteral("spellcheck"), mComposerBase->editor(), SLOT(slotCheckSpelling()));
1286 
1287     action = new QAction(i18n("Paste as Attac&hment"), this);
1288     actionCollection()->addAction(QStringLiteral("paste_att"), action);
1289     connect(action, &QAction::triggered, this, &KMComposerWin::slotPasteAsAttachment);
1290 
1291     action = new QAction(i18n("Cl&ean Spaces"), this);
1292     actionCollection()->addAction(QStringLiteral("clean_spaces"), action);
1293     connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace);
1294 
1295     mFixedFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this);
1296     actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction);
1297     connect(mFixedFontAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateFont);
1298     mFixedFontAction->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont());
1299 
1300     // these are checkable!!!
1301     mUrgentAction = new KToggleAction(i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this);
1302     actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction);
1303     mRequestMDNAction = new KToggleAction(i18n("&Request Disposition Notification"), this);
1304     actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction);
1305     mRequestMDNAction->setChecked(KMailSettings::self()->requestMDN());
1306 
1307     mRequestDeliveryConfirmation = new KToggleAction(i18n("&Request Delivery Confirmation"), this);
1308     actionCollection()->addAction(QStringLiteral("options_request_delivery_confirmation"), mRequestDeliveryConfirmation);
1309     // TOOD mRequestDeliveryConfirmation->setChecked(KMailSettings::self()->requestMDN());
1310 
1311     //----- Message-Encoding Submenu
1312     mCodecAction = new CodecAction(CodecAction::ComposerMode, this);
1313     actionCollection()->addAction(QStringLiteral("charsets"), mCodecAction);
1314     mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this);
1315     actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction);
1316     mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap());
1317     connect(mWordWrapAction, &KToggleAction::toggled, this, &KMComposerWin::slotWordWrapToggled);
1318 
1319     mSnippetAction = new KToggleAction(i18n("&Snippets"), this);
1320     actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction);
1321     connect(mSnippetAction, &KToggleAction::toggled, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged);
1322     mSnippetAction->setChecked(KMailSettings::self()->showSnippetManager());
1323 
1324     mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this);
1325     actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction);
1326     const bool spellChecking = KMailSettings::self()->autoSpellChecking();
1327     const bool useKmailEditor = !KMailSettings::self()->useExternalEditor();
1328     const bool spellCheckingEnabled = useKmailEditor && spellChecking;
1329     mAutoSpellCheckingAction->setEnabled(useKmailEditor);
1330 
1331     mAutoSpellCheckingAction->setChecked(spellCheckingEnabled);
1332     slotAutoSpellCheckingToggled(spellCheckingEnabled);
1333     connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1334     connect(mComposerBase->editor(), &KPIMTextEdit::RichTextEditor::checkSpellingChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1335 
1336     connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::textModeChanged, this, &KMComposerWin::slotTextModeChanged);
1337     connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorClosed, this, &KMComposerWin::slotExternalEditorClosed);
1338     connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorStarted, this, &KMComposerWin::slotExternalEditorStarted);
1339     // these are checkable!!!
1340     mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this);
1341     mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
1342     mMarkupAction->setIconText(i18n("Rich Text"));
1343     mMarkupAction->setToolTip(i18n("Toggle rich text editing mode"));
1344     actionCollection()->addAction(QStringLiteral("html"), mMarkupAction);
1345     connect(mMarkupAction, &KToggleAction::triggered, this, &KMComposerWin::slotToggleMarkup);
1346 
1347     mAllFieldsAction = new KToggleAction(i18n("&All Fields"), this);
1348     actionCollection()->addAction(QStringLiteral("show_all_fields"), mAllFieldsAction);
1349     connect(mAllFieldsAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1350     mIdentityAction = new KToggleAction(i18n("&Identity"), this);
1351     actionCollection()->addAction(QStringLiteral("show_identity"), mIdentityAction);
1352     connect(mIdentityAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1353     mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this);
1354     actionCollection()->addAction(QStringLiteral("show_dictionary"), mDictionaryAction);
1355     connect(mDictionaryAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1356     mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this);
1357     actionCollection()->addAction(QStringLiteral("show_fcc"), mFccAction);
1358     connect(mFccAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1359     mTransportAction = new KToggleAction(i18n("&Mail Transport"), this);
1360     actionCollection()->addAction(QStringLiteral("show_transport"), mTransportAction);
1361     connect(mTransportAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1362     mFromAction = new KToggleAction(i18n("&From"), this);
1363     actionCollection()->addAction(QStringLiteral("show_from"), mFromAction);
1364     connect(mFromAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1365     mSubjectAction = new KToggleAction(i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this);
1366     actionCollection()->addAction(QStringLiteral("show_subject"), mSubjectAction);
1367     connect(mSubjectAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1368     // end of checkable
1369 
1370     mAppendSignature = new QAction(i18n("Append S&ignature"), this);
1371     actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature);
1372     connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1373 
1374     mPrependSignature = new QAction(i18n("Pr&epend Signature"), this);
1375     actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature);
1376     connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1377 
1378     mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this);
1379     actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition);
1380     connect(mInsertSignatureAtCursorPosition,
1381             &QAction::triggered,
1382             mComposerBase->signatureController(),
1383             &MessageComposer::SignatureController::insertSignatureAtCursor);
1384 
1385     mComposerBase->attachmentController()->createActions();
1386 
1387     setStandardToolBarMenuEnabled(true);
1388 
1389     KStandardAction::keyBindings(this, &KMComposerWin::slotEditKeys, actionCollection());
1390     KStandardAction::configureToolbars(this, &KMComposerWin::slotEditToolbars, actionCollection());
1391     KStandardAction::preferences(kmkernel, &KMKernel::slotShowConfigurationDialog, actionCollection());
1392 
1393     action = new QAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Spellchecker..."), this);
1394     action->setIconText(i18n("Spellchecker"));
1395     actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action);
1396     connect(action, &QAction::triggered, this, &KMComposerWin::slotSpellcheckConfig);
1397 
1398     mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this);
1399     mEncryptAction->setIconText(i18n("Encrypt"));
1400     actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction);
1401     mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this);
1402     mSignAction->setIconText(i18n("Sign"));
1403     actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction);
1404     const auto ident = identity();
1405     // PENDING(marc): check the uses of this member and split it into
1406     // smime/openpgp and or enc/sign, if necessary:
1407     mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1408     mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1409 
1410     mLastEncryptActionState = false;
1411     mLastSignActionState = ident.pgpAutoSign();
1412 
1413     changeCryptoAction();
1414 
1415     connect(mEncryptAction, &KToggleAction::triggered, this, &KMComposerWin::slotEncryptToggled);
1416     connect(mSignAction, &KToggleAction::triggered, this, &KMComposerWin::slotSignToggled);
1417 
1418     QStringList listCryptoFormat;
1419     listCryptoFormat.reserve(numCryptoMessageFormats);
1420     for (int i = 0; i < numCryptoMessageFormats; ++i) {
1421         listCryptoFormat.push_back(Kleo::cryptoMessageFormatToLabel(cryptoMessageFormats[i]));
1422     }
1423 
1424     mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this);
1425     actionCollection()->addAction(QStringLiteral("options_select_crypto"), mCryptoModuleAction);
1426     connect(mCryptoModuleAction, &KSelectAction::indexTriggered, this, &KMComposerWin::slotCryptoModuleSelected);
1427     mCryptoModuleAction->setToolTip(i18n("Select a cryptographic format for this message"));
1428     mCryptoModuleAction->setItems(listCryptoFormat);
1429 
1430     mComposerBase->editor()->createActions(actionCollection());
1431 
1432     mFollowUpToggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("Create Follow Up Reminder..."), this);
1433     actionCollection()->addAction(QStringLiteral("follow_up_mail"), mFollowUpToggleAction);
1434     connect(mFollowUpToggleAction, &KToggleAction::triggered, this, &KMComposerWin::slotFollowUpMail);
1435     mFollowUpToggleAction->setEnabled(MessageComposer::FollowUpReminder::isAvailableAndEnabled());
1436 
1437     mPluginEditorManagerInterface->initializePlugins();
1438     mPluginEditorCheckBeforeSendManagerInterface->initializePlugins();
1439     mPluginEditorInitManagerInterface->initializePlugins();
1440     mPluginEditorConvertTextManagerInterface->initializePlugins();
1441     mPluginEditorGrammarManagerInterface->initializePlugins();
1442 
1443     mShowMenuBarAction = KStandardAction::showMenubar(this, &KMComposerWin::slotToggleMenubar, actionCollection());
1444     mShowMenuBarAction->setChecked(KMailSettings::self()->composerShowMenuBar());
1445     slotToggleMenubar(true);
1446 
1447     mHamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
1448     mHamburgerMenu->setShowMenuBarAction(mShowMenuBarAction);
1449     mHamburgerMenu->setMenuBar(menuBar());
1450     connect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, [this]() {
1451         updateHamburgerMenu();
1452         // Immediately disconnect. We only need to run this once, but on demand.
1453         // NOTE: The nullptr at the end disconnects all connections between
1454         // q and mHamburgerMenu's aboutToShowMenu signal.
1455         disconnect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, nullptr);
1456     });
1457 
1458     createGUI(QStringLiteral("kmcomposerui.rc"));
1459     initializePluginActions();
1460     connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &KMComposerWin::htmlToolBarVisibilityChanged);
1461 
1462     // In Kontact, this entry would read "Configure Kontact", but bring
1463     // up KMail's config dialog. That's sensible, though, so fix the label.
1464     QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure"));
1465     if (configureAction) {
1466         configureAction->setText(i18n("Configure KMail..."));
1467     }
1468 }
1469 
updateHamburgerMenu()1470 void KMComposerWin::updateHamburgerMenu()
1471 {
1472     QMenu *menu = new QMenu(this);
1473     menu->addAction(actionCollection()->action(QStringLiteral("new_composer")));
1474     menu->addSeparator();
1475     menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Undo))));
1476     menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Redo))));
1477     menu->addSeparator();
1478     menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Print))));
1479     menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::PrintPreview))));
1480     menu->addSeparator();
1481     menu->addAction(actionCollection()->action(QStringLiteral("attach_menu")));
1482     menu->addSeparator();
1483     menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Close))));
1484     mHamburgerMenu->setMenu(menu);
1485 }
1486 
slotToggleMenubar(bool dontShowWarning)1487 void KMComposerWin::slotToggleMenubar(bool dontShowWarning)
1488 {
1489     if (menuBar()) {
1490         if (mShowMenuBarAction->isChecked()) {
1491             menuBar()->show();
1492         } else {
1493             if (!dontShowWarning && (!toolBar()->isVisible() || !toolBar()->actions().contains(mHamburgerMenu))) {
1494                 const QString accel = mShowMenuBarAction->shortcut().toString();
1495                 KMessageBox::information(this,
1496                                          i18n("<qt>This will hide the menu bar completely."
1497                                               " You can show it again by typing %1.</qt>",
1498                                               accel),
1499                                          i18n("Hide menu bar"),
1500                                          QStringLiteral("HideMenuBarWarning"));
1501             }
1502             menuBar()->hide();
1503         }
1504         KMailSettings::self()->setComposerShowMenuBar(mShowMenuBarAction->isChecked());
1505     }
1506 }
1507 
initializePluginActions()1508 void KMComposerWin::initializePluginActions()
1509 {
1510     if (guiFactory()) {
1511         QHash<QString, QList<QAction *>> hashActions;
1512         QHashIterator<MessageComposer::PluginActionType::Type, QList<QAction *>> localEditorManagerActionsType(mPluginEditorManagerInterface->actionsType());
1513         while (localEditorManagerActionsType.hasNext()) {
1514             localEditorManagerActionsType.next();
1515             QList<QAction *> lst = localEditorManagerActionsType.value();
1516             if (!lst.isEmpty()) {
1517                 const QString actionlistname =
1518                     QLatin1String("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorManagerActionsType.key());
1519                 hashActions.insert(actionlistname, lst);
1520             }
1521         }
1522         QHashIterator<MessageComposer::PluginActionType::Type, QList<QAction *>> localEditorConvertTextManagerActionsType(
1523             mPluginEditorConvertTextManagerInterface->actionsType());
1524         while (localEditorConvertTextManagerActionsType.hasNext()) {
1525             localEditorConvertTextManagerActionsType.next();
1526             QList<QAction *> lst = localEditorConvertTextManagerActionsType.value();
1527             if (!lst.isEmpty()) {
1528                 const QString actionlistname =
1529                     QLatin1String("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorConvertTextManagerActionsType.key());
1530                 if (hashActions.contains(actionlistname)) {
1531                     lst = hashActions.value(actionlistname) + lst;
1532                     hashActions.remove(actionlistname);
1533                 }
1534                 hashActions.insert(actionlistname, lst);
1535             }
1536         }
1537 
1538         const QList<KToggleAction *> customToolsWidgetActionList = mCustomToolsWidget->actionList();
1539         const QString actionlistname =
1540             QLatin1String("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(MessageComposer::PluginActionType::Tools);
1541         for (KToggleAction *act : customToolsWidgetActionList) {
1542             QList<QAction *> lst{act};
1543             if (hashActions.contains(actionlistname)) {
1544                 lst = hashActions.value(actionlistname) + lst;
1545                 hashActions.remove(actionlistname);
1546             }
1547             hashActions.insert(actionlistname, lst);
1548         }
1549 
1550         QHash<QString, QList<QAction *>>::const_iterator i = hashActions.constBegin();
1551 
1552         while (i != hashActions.constEnd()) {
1553             const auto lst = guiFactory()->clients();
1554             for (KXMLGUIClient *client : lst) {
1555                 client->unplugActionList(i.key());
1556                 client->plugActionList(i.key(), i.value());
1557             }
1558             ++i;
1559         }
1560         // Initialize statusbar
1561         const QList<QWidget *> statusbarWidgetList = mPluginEditorManagerInterface->statusBarWidgetList();
1562         for (int index = 0; index < statusbarWidgetList.count(); ++index) {
1563             statusBar()->addPermanentWidget(statusbarWidgetList.at(index), 0);
1564         }
1565         const QList<QWidget *> statusbarWidgetListConverter = mPluginEditorConvertTextManagerInterface->statusBarWidgetList();
1566         for (int index = 0; index < statusbarWidgetListConverter.count(); ++index) {
1567             statusBar()->addPermanentWidget(statusbarWidgetListConverter.at(index), 0);
1568         }
1569     }
1570 }
1571 
changeCryptoAction()1572 void KMComposerWin::changeCryptoAction()
1573 {
1574     const auto ident = identity();
1575 
1576     if (!QGpgME::openpgp() && !QGpgME::smime()) {
1577         // no crypto whatsoever
1578         mEncryptAction->setEnabled(false);
1579         setEncryption(false);
1580         mSignAction->setEnabled(false);
1581         setSigning(false);
1582     } else {
1583         const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty();
1584         const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty();
1585 
1586         setEncryption(false);
1587         setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign());
1588     }
1589 }
1590 
setupStatusBar(QWidget * w)1591 void KMComposerWin::setupStatusBar(QWidget *w)
1592 {
1593     statusBar()->addWidget(w);
1594     mStatusbarLabel = new QLabel(this);
1595     mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
1596     statusBar()->addPermanentWidget(mStatusbarLabel);
1597 
1598     mCursorLineLabel = new QLabel(this);
1599     mCursorLineLabel->setTextFormat(Qt::PlainText);
1600     mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral("     ")));
1601     statusBar()->addPermanentWidget(mCursorLineLabel);
1602 
1603     mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral("     ")));
1604     mCursorColumnLabel->setTextFormat(Qt::PlainText);
1605     statusBar()->addPermanentWidget(mCursorColumnLabel);
1606 
1607     mStatusBarLabelToggledOverrideMode = new MessageComposer::StatusBarLabelToggledState(this);
1608     mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS"));
1609     statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0);
1610     connect(mStatusBarLabelToggledOverrideMode,
1611             &MessageComposer::StatusBarLabelToggledState::toggleModeChanged,
1612             this,
1613             &KMComposerWin::slotOverwriteModeWasChanged);
1614 
1615     mStatusBarLabelSpellCheckingChangeMode = new MessageComposer::StatusBarLabelToggledState(this);
1616     mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off"));
1617     statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0);
1618     connect(mStatusBarLabelSpellCheckingChangeMode,
1619             &MessageComposer::StatusBarLabelToggledState::toggleModeChanged,
1620             this,
1621             &KMComposerWin::slotAutoSpellCheckingToggled);
1622 }
1623 
setupEditor()1624 void KMComposerWin::setupEditor()
1625 {
1626     QFontMetrics fm(mBodyFont);
1627     mComposerBase->editor()->setTabStopDistance(fm.boundingRect(QLatin1Char(' ')).width() * 8);
1628 
1629     slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap());
1630 
1631     // Font setup
1632     slotUpdateFont();
1633 
1634     connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &KMComposerWin::slotCursorPositionChanged);
1635     slotCursorPositionChanged();
1636 }
1637 
subject() const1638 QString KMComposerWin::subject() const
1639 {
1640     return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText());
1641 }
1642 
from() const1643 QString KMComposerWin::from() const
1644 {
1645     return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text());
1646 }
1647 
slotInvalidIdentity()1648 void KMComposerWin::slotInvalidIdentity()
1649 {
1650     mIncorrectIdentityFolderWarning->identityInvalid();
1651 }
1652 
slotFccIsInvalid()1653 void KMComposerWin::slotFccIsInvalid()
1654 {
1655     mIncorrectIdentityFolderWarning->fccIsInvalid();
1656 }
1657 
setCurrentTransport(int transportId)1658 void KMComposerWin::setCurrentTransport(int transportId)
1659 {
1660     if (!mComposerBase->transportComboBox()->setCurrentTransport(transportId)) {
1661         mIncorrectIdentityFolderWarning->mailTransportIsInvalid();
1662     }
1663 }
1664 
currentIdentity() const1665 uint KMComposerWin::currentIdentity() const
1666 {
1667     return mComposerBase->identityCombo()->currentIdentity();
1668 }
1669 
addFaceHeaders(const KIdentityManagement::Identity & ident,const KMime::Message::Ptr & msg)1670 void KMComposerWin::addFaceHeaders(const KIdentityManagement::Identity &ident, const KMime::Message::Ptr &msg)
1671 {
1672     if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) {
1673         msg->removeHeader("X-Face");
1674     } else {
1675         QString xface = ident.xface();
1676         if (!xface.isEmpty()) {
1677             int numNL = (xface.length() - 1) / 70;
1678             for (int i = numNL; i > 0; --i) {
1679                 xface.insert(i * 70, QStringLiteral("\n\t"));
1680             }
1681             auto header = new KMime::Headers::Generic("X-Face");
1682             header->fromUnicodeString(xface, "utf-8");
1683             msg->setHeader(header);
1684         }
1685     }
1686 
1687     if (!ident.isFaceEnabled() || ident.face().isEmpty()) {
1688         msg->removeHeader("Face");
1689     } else {
1690         QString face = ident.face();
1691         if (!face.isEmpty()) {
1692             // The first line of data is 72 lines long to account for the
1693             // header name, the following lines are 76 lines long, like in
1694             // https://quimby.gnus.org/circus/face/
1695             if (face.length() > 72) {
1696                 int numNL = (face.length() - 73) / 76;
1697 
1698                 for (int i = numNL; i > 0; --i) {
1699                     face.insert(72 + i * 76, QStringLiteral("\n\t"));
1700                 }
1701 
1702                 face.insert(72, QStringLiteral("\n\t"));
1703             }
1704 
1705             auto header = new KMime::Headers::Generic("Face");
1706             header->fromUnicodeString(face, "utf-8");
1707             msg->setHeader(header);
1708         }
1709     }
1710 }
1711 
setMessage(const KMime::Message::Ptr & newMsg,bool lastSignState,bool lastEncryptState,bool mayAutoSign,bool allowDecryption,bool isModified)1712 void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg,
1713                                bool lastSignState,
1714                                bool lastEncryptState,
1715                                bool mayAutoSign,
1716                                bool allowDecryption,
1717                                bool isModified)
1718 {
1719     if (!newMsg) {
1720         qCDebug(KMAIL_LOG) << "newMsg == 0!";
1721         return;
1722     }
1723 
1724     if (lastSignState) {
1725         mLastSignActionState = true;
1726     }
1727 
1728     if (lastEncryptState) {
1729         mLastEncryptActionState = true;
1730     }
1731 
1732     const auto im = KMKernel::self()->identityManager();
1733 
1734     if (auto hrd = newMsg->headerByType("X-KMail-Identity")) {
1735         const QString identityStr = hrd->asUnicodeString();
1736         if (!identityStr.isEmpty()) {
1737             const auto ident = im->identityForUoid(identityStr.toUInt());
1738             if (ident.isNull()) {
1739                 if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) {
1740                     const QString identityStrName = hrd->asUnicodeString();
1741                     const auto id = im->modifyIdentityForName(identityStrName);
1742                     if (!id.isNull()) {
1743                         mId = id.uoid();
1744                     } else {
1745                         mId = 0;
1746                     }
1747                 } else {
1748                     mId = 0;
1749                 }
1750             } else {
1751                 mId = identityStr.toUInt();
1752             }
1753         }
1754     } else {
1755         if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) {
1756             const QString identityStrName = hrd->asUnicodeString();
1757             const auto id = im->modifyIdentityForName(identityStrName);
1758             if (!id.isNull()) {
1759                 mId = id.uoid();
1760             } else {
1761                 mId = 0;
1762             }
1763         } else {
1764             mId = 0;
1765         }
1766     }
1767 
1768     // don't overwrite the header values with identity specific values
1769     disconnect(mIdentityConnection);
1770 
1771     // load the mId into the gui, sticky or not, without emitting
1772     mComposerBase->identityCombo()->setCurrentIdentity(mId);
1773     mIdentityConnection = connect(mComposerBase->identityCombo(), &KIdentityManagement::IdentityCombo::identityChanged, this, [this](uint val) {
1774         slotIdentityChanged(val);
1775     });
1776 
1777     mComposerBase->setMessage(newMsg, allowDecryption);
1778     mMsg = newMsg;
1779 
1780     // manually load the identity's value into the fields; either the one from the
1781     // message, where appropriate, or the one from the sticky identity. What's in
1782     // mId might have changed meanwhile, thus the save value
1783     if (mId == 0) {
1784         mId = mComposerBase->identityCombo()->currentIdentity();
1785     }
1786     slotIdentityChanged(mId, true /*initalChange*/);
1787     // Fixing the identities with auto signing activated
1788     mLastSignActionState = mSignAction->isChecked();
1789 
1790     // Add initial data.
1791     MessageComposer::PluginEditorConverterInitialData data;
1792     data.setMewMsg(mMsg);
1793     data.setNewMessage(mContext == TemplateContext::New);
1794     mPluginEditorConvertTextManagerInterface->setInitialData(data);
1795 
1796     mEdtFrom->setText(mMsg->from()->asUnicodeString());
1797     mEdtSubject->setText(mMsg->subject()->asUnicodeString());
1798 
1799     // Restore the quote prefix. We can't just use the global quote prefix here,
1800     // since the prefix is different for each message, it might for example depend
1801     // on the original sender in a reply.
1802     if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) {
1803         mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString());
1804     }
1805 
1806     // check for the presence of a DNT header, indicating that MDN's were requested
1807     if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) {
1808         const QString mdnAddr = hdr->asUnicodeString();
1809         mRequestMDNAction->setChecked((!mdnAddr.isEmpty() && im->thatIsMe(mdnAddr)) || KMailSettings::self()->requestMDN());
1810     }
1811     if (auto hdr = newMsg->headerByType("Return-Receipt-To")) {
1812         const QString returnReceiptToAddr = hdr->asUnicodeString();
1813         mRequestDeliveryConfirmation->setChecked((!returnReceiptToAddr.isEmpty() && im->thatIsMe(returnReceiptToAddr))
1814                                                  /*TODO || KMailSettings::self()->requestMDN()*/);
1815     }
1816     // check for presence of a priority header, indicating urgent mail:
1817     if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) {
1818         const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString();
1819         const QString priority = newMsg->headerByType("Priority")->asUnicodeString();
1820         if (xpriority == QLatin1String("2 (High)") && priority == QLatin1String("urgent")) {
1821             mUrgentAction->setChecked(true);
1822         }
1823     }
1824 
1825     const auto &ident = identity();
1826 
1827     addFaceHeaders(ident, mMsg);
1828 
1829     // if these headers are present, the state of the message should be overruled
1830     MessageComposer::DraftSignatureState signState(mMsg);
1831     if (signState.isDefined()) {
1832         mLastSignActionState = signState.signState();
1833     }
1834     MessageComposer::DraftEncryptionState encState(mMsg);
1835     if (encState.isDefined()) {
1836         mLastEncryptActionState = encState.encryptionState();
1837     }
1838     MessageComposer::DraftCryptoMessageFormatState formatState(mMsg);
1839     if (formatState.isDefined()) {
1840         mCryptoModuleAction->setCurrentItem(format2cb(formatState.cryptoMessageFormatState()));
1841     }
1842 
1843     mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1844     mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1845 
1846     if (QGpgME::openpgp() || QGpgME::smime()) {
1847         const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty();
1848         const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty();
1849 
1850         setEncryption(mLastEncryptActionState);
1851         setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState);
1852     }
1853     updateSignatureAndEncryptionStateIndicators();
1854 
1855     QString kmailFcc;
1856     if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) {
1857         kmailFcc = hdr->asUnicodeString();
1858     }
1859     if (kmailFcc.isEmpty()) {
1860         setFcc(ident.fcc());
1861     } else {
1862         setFcc(kmailFcc);
1863     }
1864     if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) {
1865         const QString dictionary = hdr->asUnicodeString();
1866         if (!dictionary.isEmpty()) {
1867             if (!mComposerBase->dictionary()->assignByDictionnary(dictionary)) {
1868                 mIncorrectIdentityFolderWarning->dictionaryInvalid();
1869             }
1870         }
1871     } else {
1872         mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
1873     }
1874 
1875     auto msgContent = new KMime::Content;
1876     msgContent->setContent(mMsg->encodedContent());
1877     msgContent->parse();
1878     MimeTreeParser::SimpleObjectTreeSource emptySource;
1879     MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
1880     emptySource.setDecryptMessage(allowDecryption);
1881     otp.parseObjectTree(msgContent);
1882 
1883     bool shouldSetCharset = false;
1884     if ((mContext == Reply || mContext == ReplyToAll || mContext == Forward) && MessageComposer::MessageComposerSettings::forceReplyCharset()) {
1885         shouldSetCharset = true;
1886     }
1887     if (shouldSetCharset && !otp.plainTextContentCharset().isEmpty()) {
1888         mOriginalPreferredCharset = otp.plainTextContentCharset();
1889     }
1890     // always set auto charset, but prefer original when composing if force reply is set.
1891     mCodecAction->setAutoCharset();
1892 
1893     delete msgContent;
1894 
1895     if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign) {
1896         //
1897         // Espen 2000-05-16
1898         // Delay the signature appending. It may start a fileseletor.
1899         // Not user friendly if this modal fileseletor opens before the
1900         // composer.
1901         //
1902         if (MessageComposer::MessageComposerSettings::self()->prependSignature()) {
1903             QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1904         } else {
1905             QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1906         }
1907     } else {
1908         mComposerBase->editor()->externalComposer()->startExternalEditor();
1909     }
1910 
1911     setModified(isModified);
1912 
1913     // honor "keep reply in this folder" setting even when the identity is changed later on
1914     mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc);
1915     QTimer::singleShot(
1916         0,
1917         this,
1918         &KMComposerWin::forceAutoSaveMessage); // Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in.
1919 }
1920 
setAutoSaveFileName(const QString & fileName)1921 void KMComposerWin::setAutoSaveFileName(const QString &fileName)
1922 {
1923     mComposerBase->setAutoSaveFileName(fileName);
1924 }
1925 
setSigningAndEncryptionDisabled(bool v)1926 void KMComposerWin::setSigningAndEncryptionDisabled(bool v)
1927 {
1928     mSigningAndEncryptionExplicitlyDisabled = v;
1929 }
1930 
setFolder(const Akonadi::Collection & aFolder)1931 void KMComposerWin::setFolder(const Akonadi::Collection &aFolder)
1932 {
1933     mFolder = aFolder;
1934 }
1935 
setFcc(const QString & idString)1936 void KMComposerWin::setFcc(const QString &idString)
1937 {
1938     // check if the sent-mail folder still exists
1939     Akonadi::Collection col;
1940     if (idString.isEmpty()) {
1941         col = CommonKernel->sentCollectionFolder();
1942     } else {
1943         col = Akonadi::Collection(idString.toLongLong());
1944     }
1945     if (col.isValid()) {
1946         mComposerBase->setFcc(col);
1947         mFccFolder->setCollection(col);
1948         mIncorrectIdentityFolderWarning->clearFccInvalid();
1949     } else {
1950         mIncorrectIdentityFolderWarning->fccIsInvalid();
1951         qCWarning(KMAIL_LOG) << "setFcc: collection invalid " << idString;
1952     }
1953 }
1954 
isComposerModified() const1955 bool KMComposerWin::isComposerModified() const
1956 {
1957     return mComposerBase->editor()->document()->isModified() || mEdtFrom->isModified() || mComposerBase->recipientsEditor()->isModified()
1958         || mEdtSubject->document()->isModified();
1959 }
1960 
isModified() const1961 bool KMComposerWin::isModified() const
1962 {
1963     return mWasModified || isComposerModified();
1964 }
1965 
setModified(bool modified)1966 void KMComposerWin::setModified(bool modified)
1967 {
1968     mWasModified = modified;
1969     changeModifiedState(modified);
1970 }
1971 
changeModifiedState(bool modified)1972 void KMComposerWin::changeModifiedState(bool modified)
1973 {
1974     mComposerBase->editor()->document()->setModified(modified);
1975     if (!modified) {
1976         mEdtFrom->setModified(false);
1977         mComposerBase->recipientsEditor()->clearModified();
1978         mEdtSubject->document()->setModified(false);
1979     }
1980 }
1981 
queryClose()1982 bool KMComposerWin::queryClose()
1983 {
1984     if (!mComposerBase->editor()->checkExternalEditorFinished()) {
1985         return false;
1986     }
1987     if (kmkernel->shuttingDown() || qApp->isSavingSession()) {
1988         writeConfig();
1989         return true;
1990     }
1991 
1992     if (isModified()) {
1993         const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder));
1994         const QString savebut = (istemplate ? i18n("Re&save as Template") : i18n("&Save as Draft"));
1995         const QString savetext = (istemplate ? i18n("Resave this message in the Templates folder. "
1996                                                     "It can then be used at a later time.")
1997                                              : i18n("Save this message in the Drafts folder. "
1998                                                     "It can then be edited and sent at a later time."));
1999 
2000         const int rc = KMessageBox::warningYesNoCancel(this,
2001                                                        i18n("Do you want to save the message for later or discard it?"),
2002                                                        i18n("Close Composer"),
2003                                                        KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext),
2004                                                        KStandardGuiItem::discard(),
2005                                                        KStandardGuiItem::cancel());
2006         if (rc == KMessageBox::Cancel) {
2007             return false;
2008         } else if (rc == KMessageBox::Yes) {
2009             // doSend will close the window. Just return false from this method
2010             if (istemplate) {
2011                 slotSaveTemplate();
2012             } else {
2013                 slotSaveDraft();
2014             }
2015             return false;
2016         }
2017         // else fall through: return true
2018     }
2019     mComposerBase->cleanupAutoSave();
2020 
2021     if (!mMiscComposers.isEmpty()) {
2022         qCWarning(KMAIL_LOG) << "Tried to close while composer was active";
2023         return false;
2024     }
2025     writeConfig();
2026     return true;
2027 }
2028 
userForgotAttachment()2029 MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment()
2030 {
2031     const bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning();
2032 
2033     if (!checkForForgottenAttachments) {
2034         return MessageComposer::ComposerViewBase::NoMissingAttachmentFound;
2035     }
2036 
2037     mComposerBase->setSubject(subject()); // be sure the composer knows the subject
2038     const MessageComposer::ComposerViewBase::MissingAttachment missingAttachments =
2039         mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords());
2040 
2041     return missingAttachments;
2042 }
2043 
forceAutoSaveMessage()2044 void KMComposerWin::forceAutoSaveMessage()
2045 {
2046     autoSaveMessage(true);
2047 }
2048 
autoSaveMessage(bool force)2049 void KMComposerWin::autoSaveMessage(bool force)
2050 {
2051     if (isComposerModified() || force) {
2052         applyComposerSetting(mComposerBase);
2053         mComposerBase->saveMailSettings();
2054         mComposerBase->autoSaveMessage();
2055         if (!force) {
2056             mWasModified = true;
2057             changeModifiedState(false);
2058         }
2059     } else {
2060         mComposerBase->updateAutoSave();
2061     }
2062 }
2063 
encryptToSelf() const2064 bool KMComposerWin::encryptToSelf() const
2065 {
2066     return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf();
2067 }
2068 
slotSendFailed(const QString & msg,MessageComposer::ComposerViewBase::FailedType type)2069 void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type)
2070 {
2071     setEnabled(true);
2072     if (!msg.isEmpty()) {
2073         KMessageBox::sorry(mMainWidget,
2074                            msg,
2075                            (type == MessageComposer::ComposerViewBase::AutoSave) ? i18n("Autosave Message Failed") : i18n("Sending Message Failed"));
2076     }
2077 }
2078 
slotSendSuccessful(Akonadi::Item::Id id)2079 void KMComposerWin::slotSendSuccessful(Akonadi::Item::Id id)
2080 {
2081     if (id != -1) {
2082         UndoSendManager::self()->addItem(id, subject(), KMailSettings::self()->undoSendDelay());
2083     }
2084     setModified(false);
2085     mComposerBase->cleanupAutoSave();
2086     mFolder = Akonadi::Collection(); // see dtor
2087     close();
2088 }
2089 
identity() const2090 const KIdentityManagement::Identity &KMComposerWin::identity() const
2091 {
2092     return KMKernel::self()->identityManager()->identityForUoidOrDefault(currentIdentity());
2093 }
2094 
cryptoMessageFormat() const2095 Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const
2096 {
2097     if (!mCryptoModuleAction) {
2098         return Kleo::AutoFormat;
2099     }
2100     return cb2format(mCryptoModuleAction->currentItem());
2101 }
2102 
addAttach(KMime::Content * msgPart)2103 void KMComposerWin::addAttach(KMime::Content *msgPart)
2104 {
2105     mComposerBase->addAttachmentPart(msgPart);
2106     setModified(true);
2107 }
2108 
slotAddressBook()2109 void KMComposerWin::slotAddressBook()
2110 {
2111     auto job = new KIO::CommandLauncherJob(QStringLiteral("kaddressbook"), {}, this);
2112     job->setDesktopName(QStringLiteral("org.kde.kaddressbook"));
2113     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
2114     job->start();
2115 }
2116 
slotInsertFile()2117 void KMComposerWin::slotInsertFile()
2118 {
2119     const QUrl u = insertFile();
2120     if (u.isEmpty()) {
2121         return;
2122     }
2123 
2124     mRecentAction->addUrl(u);
2125     // Prevent race condition updating list when multiple composers are open
2126     {
2127         QUrlQuery query(u);
2128         const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset")));
2129         QStringList urls = KMailSettings::self()->recentUrls();
2130         QStringList encodings = KMailSettings::self()->recentEncodings();
2131         // Prevent config file from growing without bound
2132         // Would be nicer to get this constant from KRecentFilesAction
2133         const int mMaxRecentFiles = 30;
2134         while (urls.count() > mMaxRecentFiles) {
2135             urls.removeLast();
2136         }
2137         while (encodings.count() > mMaxRecentFiles) {
2138             encodings.removeLast();
2139         }
2140         // sanity check
2141         if (urls.count() != encodings.count()) {
2142             urls.clear();
2143             encodings.clear();
2144         }
2145         urls.prepend(u.toDisplayString());
2146         encodings.prepend(encoding);
2147         KMailSettings::self()->setRecentUrls(urls);
2148         KMailSettings::self()->setRecentEncodings(encodings);
2149         KMailSettings::self()->save();
2150     }
2151     slotInsertRecentFile(u);
2152 }
2153 
slotRecentListFileClear()2154 void KMComposerWin::slotRecentListFileClear()
2155 {
2156     KSharedConfig::Ptr config = KMKernel::self()->config();
2157     KConfigGroup group(config, "Composer");
2158     group.deleteEntry("recent-urls");
2159     group.deleteEntry("recent-encoding");
2160     KMailSettings::self()->save();
2161 }
2162 
slotInsertRecentFile(const QUrl & u)2163 void KMComposerWin::slotInsertRecentFile(const QUrl &u)
2164 {
2165     if (u.fileName().isEmpty()) {
2166         return;
2167     }
2168 
2169     // Get the encoding previously used when inserting this file
2170     QString encoding;
2171     const QStringList urls = KMailSettings::self()->recentUrls();
2172     const QStringList encodings = KMailSettings::self()->recentEncodings();
2173     const int index = urls.indexOf(u.toDisplayString());
2174     if (index != -1) {
2175         encoding = encodings[index];
2176     } else {
2177         qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; // see InsertTextFileJob
2178         return;
2179     }
2180     auto job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u);
2181     job->setEncoding(encoding);
2182     connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile);
2183     job->start();
2184 }
2185 
showErrorMessage(KJob * job)2186 bool KMComposerWin::showErrorMessage(KJob *job)
2187 {
2188     if (job->error()) {
2189         if (auto uiDelegate = static_cast<KIO::Job *>(job)->uiDelegate()) {
2190             uiDelegate->showErrorMessage();
2191         } else {
2192             qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString();
2193         }
2194         return true;
2195     }
2196     return false;
2197 }
2198 
slotInsertTextFile(KJob * job)2199 void KMComposerWin::slotInsertTextFile(KJob *job)
2200 {
2201     showErrorMessage(job);
2202 }
2203 
slotCryptoModuleSelected()2204 void KMComposerWin::slotCryptoModuleSelected()
2205 {
2206     slotSelectCryptoModule(false);
2207 }
2208 
slotSelectCryptoModule(bool init)2209 void KMComposerWin::slotSelectCryptoModule(bool init)
2210 {
2211     if (!init) {
2212         setModified(true);
2213     }
2214 
2215     mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments());
2216     mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments());
2217 }
2218 
slotUpdateFont()2219 void KMComposerWin::slotUpdateFont()
2220 {
2221     if (!mFixedFontAction) {
2222         return;
2223     }
2224     const QFont plaintextFont = mFixedFontAction->isChecked() ? mFixedFont : mBodyFont;
2225     mComposerBase->editor()->composerControler()->setFontForWholeText(plaintextFont);
2226     mComposerBase->editor()->setDefaultFontSize(plaintextFont.pointSize());
2227 }
2228 
insertFile()2229 QUrl KMComposerWin::insertFile()
2230 {
2231     const KEncodingFileDialog::Result result =
2232         KEncodingFileDialog::getOpenUrlAndEncoding(QString(), QUrl(), QString(), this, i18nc("@title:window", "Insert File"));
2233     QUrl url;
2234     if (!result.URLs.isEmpty()) {
2235         url = result.URLs.constFirst();
2236         if (url.isValid()) {
2237             MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding));
2238         }
2239     }
2240     return url;
2241 }
2242 
smartQuote(const QString & msg)2243 QString KMComposerWin::smartQuote(const QString &msg)
2244 {
2245     return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
2246 }
2247 
insertUrls(const QMimeData * source,const QList<QUrl> & urlList)2248 void KMComposerWin::insertUrls(const QMimeData *source, const QList<QUrl> &urlList)
2249 {
2250     QStringList urlAdded;
2251     for (const QUrl &url : urlList) {
2252         QString urlStr;
2253         if (url.scheme() == QLatin1String("mailto")) {
2254             urlStr = KEmailAddress::decodeMailtoUrl(url);
2255         } else {
2256             urlStr = url.toDisplayString();
2257             // Workaround #346370
2258             if (urlStr.isEmpty()) {
2259                 urlStr = source->text();
2260             }
2261         }
2262         if (!urlAdded.contains(urlStr)) {
2263             mComposerBase->editor()->composerControler()->insertLink(urlStr);
2264             urlAdded.append(urlStr);
2265         }
2266     }
2267 }
2268 
insertFromMimeData(const QMimeData * source,bool forceAttachment)2269 bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment)
2270 {
2271     // If this is a PNG image, either add it as an attachment or as an inline image
2272     if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) {
2273         const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html")));
2274         mComposerBase->editor()->insertHtml(html);
2275         return true;
2276     } else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText()
2277                && !forceAttachment) {
2278         mComposerBase->editor()->insertPlainText(source->text());
2279         return true;
2280     } else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) {
2281         // Get the image data before showing the dialog, since that processes events which can delete
2282         // the QMimeData object behind our back
2283         const QByteArray imageData = source->data(QStringLiteral("image/png"));
2284         if (imageData.isEmpty()) {
2285             return true;
2286         }
2287         if (!forceAttachment) {
2288             if (mComposerBase->editor()->textMode()
2289                 == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
2290                 auto image = qvariant_cast<QImage>(source->imageData());
2291                 QFileInfo fi(source->text());
2292 
2293                 QMenu menu(this);
2294                 const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image"));
2295                 menu.addAction(i18n("Add as &Attachment"));
2296                 const QAction *selectedAction = menu.exec(QCursor::pos());
2297                 if (selectedAction == addAsInlineImageAction) {
2298                     // Let the textedit from kdepimlibs handle inline images
2299                     mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi);
2300                     return true;
2301                 } else if (!selectedAction) {
2302                     return true;
2303                 }
2304                 // else fall through
2305             }
2306         }
2307         // Ok, when we reached this point, the user wants to add the image as an attachment.
2308         // Ask for the filename first.
2309         bool ok;
2310         QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
2311         if (!ok) {
2312             return true;
2313         }
2314         attName = attName.trimmed();
2315         if (attName.isEmpty()) {
2316             KMessageBox::sorry(this, i18n("Attachment name can't be empty"), i18n("Invalid Attachment Name"));
2317 
2318             return true;
2319         }
2320         addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png");
2321         return true;
2322     } else {
2323         auto job = new DndFromArkJob(this);
2324         job->setComposerWin(this);
2325         if (job->extract(source)) {
2326             return true;
2327         }
2328     }
2329 
2330     // If this is a URL list, add those files as attachments or text
2331     // but do not offer this if we are pasting plain text containing an url, e.g. from a browser
2332     const QList<QUrl> urlList = source->urls();
2333     if (!urlList.isEmpty()) {
2334         // Search if it's message items.
2335         Akonadi::Item::List items;
2336         Akonadi::Collection::List collections;
2337         bool allLocalURLs = true;
2338 
2339         for (const QUrl &url : urlList) {
2340             if (!url.isLocalFile()) {
2341                 allLocalURLs = false;
2342             }
2343             const Akonadi::Item item = Akonadi::Item::fromUrl(url);
2344             if (item.isValid()) {
2345                 items << item;
2346             } else {
2347                 const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url);
2348                 if (collection.isValid()) {
2349                     collections << collection;
2350                 }
2351             }
2352         }
2353 
2354         if (items.isEmpty() && collections.isEmpty()) {
2355             if (allLocalURLs || forceAttachment) {
2356                 QVector<AttachmentInfo> infoList;
2357                 infoList.reserve(urlList.count());
2358                 for (const QUrl &url : urlList) {
2359                     AttachmentInfo info;
2360                     info.url = url;
2361                     infoList.append(info);
2362                 }
2363                 addAttachment(infoList, false);
2364             } else {
2365                 QMenu p;
2366                 const int sizeUrl(urlList.size());
2367                 const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl));
2368                 const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl));
2369                 const QAction *selectedAction = p.exec(QCursor::pos());
2370 
2371                 if (selectedAction == addAsTextAction) {
2372                     insertUrls(source, urlList);
2373                 } else if (selectedAction == addAsAttachmentAction) {
2374                     QVector<AttachmentInfo> infoList;
2375                     for (const QUrl &url : urlList) {
2376                         if (url.isValid()) {
2377                             AttachmentInfo info;
2378                             info.url = url;
2379                             infoList.append(info);
2380                         }
2381                     }
2382                     addAttachment(infoList, false);
2383                 }
2384             }
2385             return true;
2386         } else {
2387             if (!items.isEmpty()) {
2388                 auto itemFetchJob = new Akonadi::ItemFetchJob(items, this);
2389                 itemFetchJob->fetchScope().fetchFullPayload(true);
2390                 itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
2391                 connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob);
2392             }
2393             if (!collections.isEmpty()) {
2394                 qCDebug(KMAIL_LOG) << "Collection dnd not supported";
2395             }
2396             return true;
2397         }
2398     }
2399     return false;
2400 }
2401 
slotPasteAsAttachment()2402 void KMComposerWin::slotPasteAsAttachment()
2403 {
2404     const QMimeData *mimeData = QApplication::clipboard()->mimeData();
2405     if (!mimeData) {
2406         return;
2407     }
2408     if (insertFromMimeData(mimeData, true)) {
2409         return;
2410     }
2411     if (mimeData->hasText()) {
2412         bool ok;
2413         const QString attName =
2414             QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
2415         if (ok) {
2416             mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
2417         }
2418         return;
2419     }
2420 }
2421 
slotFetchJob(KJob * job)2422 void KMComposerWin::slotFetchJob(KJob *job)
2423 {
2424     if (showErrorMessage(job)) {
2425         return;
2426     }
2427     auto fjob = qobject_cast<Akonadi::ItemFetchJob *>(job);
2428     if (!fjob) {
2429         return;
2430     }
2431     const Akonadi::Item::List items = fjob->items();
2432 
2433     if (items.isEmpty()) {
2434         return;
2435     }
2436 
2437     if (items.constFirst().mimeType() == KMime::Message::mimeType()) {
2438         uint identity = 0;
2439         if (items.at(0).isValid()) {
2440             const Akonadi::Collection parentCollection = items.at(0).parentCollection();
2441             if (parentCollection.isValid()) {
2442                 const QString resourceName = parentCollection.resource();
2443                 if (!resourceName.isEmpty()) {
2444                     QSharedPointer<MailCommon::FolderSettings> fd(MailCommon::FolderSettings::forCollection(parentCollection, false));
2445                     if (fd) {
2446                         identity = fd->identity();
2447                     }
2448                 }
2449             }
2450         }
2451         KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this);
2452         command->start();
2453     } else {
2454         for (const Akonadi::Item &item : items) {
2455             QString attachmentName = QStringLiteral("attachment");
2456             if (item.hasPayload<KContacts::Addressee>()) {
2457                 const auto contact = item.payload<KContacts::Addressee>();
2458                 attachmentName = contact.realName() + QLatin1String(".vcf");
2459                 // Workaround about broken kaddressbook fields.
2460                 QByteArray data = item.payloadData();
2461                 KContacts::adaptIMAttributes(data);
2462                 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard");
2463             } else if (item.hasPayload<KContacts::ContactGroup>()) {
2464                 const auto group = item.payload<KContacts::ContactGroup>();
2465                 attachmentName = group.name() + QLatin1String(".vcf");
2466                 auto expandJob = new Akonadi::ContactGroupExpandJob(group, this);
2467                 expandJob->setProperty("groupName", attachmentName);
2468                 connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult);
2469                 expandJob->start();
2470             } else {
2471                 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1());
2472             }
2473         }
2474     }
2475 }
2476 
slotExpandGroupResult(KJob * job)2477 void KMComposerWin::slotExpandGroupResult(KJob *job)
2478 {
2479     auto expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job);
2480     Q_ASSERT(expandJob);
2481 
2482     const QString attachmentName = expandJob->property("groupName").toString();
2483     KContacts::VCardConverter converter;
2484     const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0);
2485     if (!groupData.isEmpty()) {
2486         addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard");
2487     }
2488 }
2489 
slotClose()2490 void KMComposerWin::slotClose()
2491 {
2492     close();
2493 }
2494 
slotNewComposer()2495 void KMComposerWin::slotNewComposer()
2496 {
2497     auto job = new KMComposerCreateNewComposerJob;
2498     job->setCollectionForNewMessage(mCollectionForNewMessage);
2499 
2500     job->setCurrentIdentity(currentIdentity());
2501     job->start();
2502 }
2503 
slotUpdateWindowTitle()2504 void KMComposerWin::slotUpdateWindowTitle()
2505 {
2506     QString s(mEdtSubject->toPlainText());
2507     mComposerBase->setSubject(s);
2508     // Remove characters that show badly in most window decorations:
2509     // newlines tend to become boxes.
2510     if (s.isEmpty()) {
2511         setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')'));
2512     } else {
2513         setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' ')));
2514     }
2515 }
2516 
slotEncryptToggled(bool on)2517 void KMComposerWin::slotEncryptToggled(bool on)
2518 {
2519     setEncryption(on, true);
2520     updateSignatureAndEncryptionStateIndicators();
2521 }
2522 
setEncryption(bool encrypt,bool setByUser)2523 void KMComposerWin::setEncryption(bool encrypt, bool setByUser)
2524 {
2525     const bool wasModified = isModified();
2526     if (setByUser) {
2527         setModified(true);
2528     }
2529     if (!mEncryptAction->isEnabled()) {
2530         encrypt = false;
2531     }
2532     // check if the user wants to encrypt messages to himself and if he defined
2533     // an encryption key for the current identity
2534     else if (encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey) {
2535         if (setByUser) {
2536             KMessageBox::sorry(this,
2537                                i18n("<qt><p>You have requested that messages be "
2538                                     "encrypted to yourself, but the currently selected "
2539                                     "identity does not define an (OpenPGP or S/MIME) "
2540                                     "encryption key to use for this.</p>"
2541                                     "<p>Please select the key(s) to use "
2542                                     "in the identity configuration.</p>"
2543                                     "</qt>"),
2544                                i18n("Undefined Encryption Key"));
2545             setModified(wasModified);
2546         }
2547         encrypt = false;
2548     }
2549 
2550     // make sure the mEncryptAction is in the right state
2551     mEncryptAction->setChecked(encrypt);
2552     mEncryptAction->setProperty("setByUser", setByUser);
2553     if (!setByUser) {
2554         updateSignatureAndEncryptionStateIndicators();
2555     }
2556 
2557     // show the appropriate icon
2558     if (encrypt) {
2559         mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt")));
2560     } else {
2561         mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-decrypt")));
2562     }
2563 
2564     if (setByUser) {
2565         // User has toggled encryption, go over all recipients
2566         const auto lst = mComposerBase->recipientsEditor()->lines();
2567         for (auto line : lst) {
2568             if (encrypt) {
2569                 // Encryption was enabled, update encryption status of all recipients
2570                 slotRecipientAdded(qobject_cast<MessageComposer::RecipientLineNG *>(line));
2571             } else {
2572                 // Encryption was disabled, remove the encryption indicator
2573                 auto edit = qobject_cast<MessageComposer::RecipientLineNG *>(line);
2574                 edit->setIcon(QIcon());
2575                 auto recipient = edit->data().dynamicCast<MessageComposer::Recipient>();
2576                 recipient->setEncryptionAction(Kleo::Impossible);
2577                 recipient->setKey(GpgME::Key());
2578             }
2579         }
2580     }
2581 
2582     // mark the attachments for (no) encryption
2583     if (canSignEncryptAttachments()) {
2584         mComposerBase->attachmentModel()->setEncryptSelected(encrypt);
2585     }
2586 }
2587 
slotSignToggled(bool on)2588 void KMComposerWin::slotSignToggled(bool on)
2589 {
2590     setSigning(on, true);
2591     updateSignatureAndEncryptionStateIndicators();
2592 }
2593 
setSigning(bool sign,bool setByUser)2594 void KMComposerWin::setSigning(bool sign, bool setByUser)
2595 {
2596     const bool wasModified = isModified();
2597     if (setByUser) {
2598         setModified(true);
2599     }
2600     if (!mSignAction->isEnabled()) {
2601         sign = false;
2602     }
2603 
2604     // check if the user defined a signing key for the current identity
2605     if (sign && !mLastIdentityHasSigningKey) {
2606         if (setByUser) {
2607             KMessageBox::sorry(this,
2608                                i18n("<qt><p>In order to be able to sign "
2609                                     "this message you first have to "
2610                                     "define the (OpenPGP or S/MIME) signing key "
2611                                     "to use.</p>"
2612                                     "<p>Please select the key to use "
2613                                     "in the identity configuration.</p>"
2614                                     "</qt>"),
2615                                i18n("Undefined Signing Key"));
2616             setModified(wasModified);
2617         }
2618         sign = false;
2619     }
2620 
2621     // make sure the mSignAction is in the right state
2622     mSignAction->setChecked(sign);
2623 
2624     if (!setByUser) {
2625         updateSignatureAndEncryptionStateIndicators();
2626     }
2627     // mark the attachments for (no) signing
2628     if (canSignEncryptAttachments()) {
2629         mComposerBase->attachmentModel()->setSignSelected(sign);
2630     }
2631 }
2632 
slotWordWrapToggled(bool on)2633 void KMComposerWin::slotWordWrapToggled(bool on)
2634 {
2635     if (on) {
2636         mComposerBase->editor()->enableWordWrap(validateLineWrapWidth());
2637     } else {
2638         disableWordWrap();
2639     }
2640 }
2641 
validateLineWrapWidth() const2642 int KMComposerWin::validateLineWrapWidth() const
2643 {
2644     int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
2645     if ((lineWrap == 0) || (lineWrap > 78)) {
2646         lineWrap = 78;
2647     } else if (lineWrap < 30) {
2648         lineWrap = 30;
2649     }
2650     return lineWrap;
2651 }
2652 
disableWordWrap()2653 void KMComposerWin::disableWordWrap()
2654 {
2655     mComposerBase->editor()->disableWordWrap();
2656 }
2657 
forceDisableHtml()2658 void KMComposerWin::forceDisableHtml()
2659 {
2660     mForceDisableHtml = true;
2661     disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2662     mMarkupAction->setEnabled(false);
2663     // FIXME: Remove the toggle toolbar action somehow
2664 }
2665 
isComposing() const2666 bool KMComposerWin::isComposing() const
2667 {
2668     return mComposerBase && mComposerBase->isComposing();
2669 }
2670 
disableForgottenAttachmentsCheck()2671 void KMComposerWin::disableForgottenAttachmentsCheck()
2672 {
2673     mCheckForForgottenAttachments = false;
2674 }
2675 
slotPrint()2676 void KMComposerWin::slotPrint()
2677 {
2678     printComposer(false);
2679 }
2680 
slotPrintPreview()2681 void KMComposerWin::slotPrintPreview()
2682 {
2683     printComposer(true);
2684 }
2685 
printComposer(bool preview)2686 void KMComposerWin::printComposer(bool preview)
2687 {
2688     MessageComposer::Composer *composer = new MessageComposer::Composer();
2689     mComposerBase->fillComposer(composer);
2690     mMiscComposers.append(composer);
2691     composer->setProperty("preview", preview);
2692     connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult);
2693     composer->start();
2694 }
2695 
slotPrintComposeResult(KJob * job)2696 void KMComposerWin::slotPrintComposeResult(KJob *job)
2697 {
2698     const bool preview = job->property("preview").toBool();
2699     printComposeResult(job, preview);
2700 }
2701 
printComposeResult(KJob * job,bool preview)2702 void KMComposerWin::printComposeResult(KJob *job, bool preview)
2703 {
2704     Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
2705     auto composer = qobject_cast<MessageComposer::Composer *>(job);
2706     Q_ASSERT(mMiscComposers.contains(composer));
2707     mMiscComposers.removeAll(composer);
2708 
2709     if (composer->error() == MessageComposer::Composer::NoError) {
2710         Q_ASSERT(composer->resultMessages().size() == 1);
2711         Akonadi::Item printItem;
2712         printItem.setPayload<KMime::Message::Ptr>(composer->resultMessages().constFirst());
2713         Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().constFirst()), printItem);
2714         const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich;
2715         const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text;
2716         KMPrintCommandInfo commandInfo;
2717         commandInfo.mMsg = printItem;
2718         commandInfo.mFormat = format;
2719         commandInfo.mHtmlLoadExtOverride = isHtml;
2720         commandInfo.mPrintPreview = preview;
2721         auto command = new KMPrintCommand(this, commandInfo);
2722         command->start();
2723     } else {
2724         showErrorMessage(job);
2725     }
2726 }
2727 
doSend(MessageComposer::MessageSender::SendMethod method,MessageComposer::MessageSender::SaveIn saveIn,bool willSendItWithoutReediting)2728 void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool willSendItWithoutReediting)
2729 {
2730     if (saveIn == MessageComposer::MessageSender::SaveInNone) {
2731         const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment();
2732         if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment)
2733             || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) {
2734             return;
2735         }
2736     }
2737 
2738     // TODO generate new message from plugins.
2739     MessageComposer::PluginEditorConverterBeforeConvertingData data;
2740     data.setNewMessage(mContext == TemplateContext::New);
2741     mPluginEditorConvertTextManagerInterface->setDataBeforeConvertingText(data);
2742 
2743     // TODO converttext if necessary
2744 
2745     // TODO integrate with MDA online status
2746     if (method == MessageComposer::MessageSender::SendImmediate) {
2747         if (!MessageComposer::Util::sendMailDispatcherIsOnline()) {
2748             method = MessageComposer::MessageSender::SendLater;
2749         }
2750         if (KMailSettings::self()->enabledUndoSend()) {
2751             mComposerBase->setSendLaterInfo(nullptr);
2752             const bool wasRegistered = sendLaterRegistered();
2753             if (wasRegistered) {
2754                 auto info = new MessageComposer::SendLaterInfo;
2755                 info->setRecurrence(false);
2756                 info->setSubject(subject());
2757                 info->setDateTime(QDateTime::currentDateTime().addSecs(KMailSettings::self()->undoSendDelay()));
2758                 mComposerBase->setSendLaterInfo(info);
2759             }
2760             method = MessageComposer::MessageSender::SendLater;
2761             willSendItWithoutReediting = true;
2762             saveIn = MessageComposer::MessageSender::SaveInOutbox;
2763         }
2764     }
2765 
2766     if (saveIn == MessageComposer::MessageSender::SaveInNone || willSendItWithoutReediting) { // don't save as draft or template, send immediately
2767         if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2768             if (!(mShowHeaders & HDR_FROM)) {
2769                 mShowHeaders |= HDR_FROM;
2770                 rethinkFields(false);
2771             }
2772             mEdtFrom->setFocus();
2773             KMessageBox::sorry(this,
2774                                i18n("You must enter your email address in the "
2775                                     "From: field. You should also set your email "
2776                                     "address for all identities, so that you do "
2777                                     "not have to enter it for each message."));
2778             return;
2779         }
2780         if (mComposerBase->to().isEmpty()) {
2781             if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) {
2782                 KMessageBox::information(this,
2783                                          i18n("You must specify at least one receiver, "
2784                                               "either in the To: field or as CC or as BCC."));
2785 
2786                 return;
2787             } else {
2788                 const int rc = KMessageBox::questionYesNo(this,
2789                                                           i18n("To: field is empty. "
2790                                                                "Send message anyway?"),
2791                                                           i18n("No To: specified"),
2792                                                           KGuiItem(i18n("S&end as Is")),
2793                                                           KGuiItem(i18n("&Specify the To field")));
2794                 if (rc == KMessageBox::No) {
2795                     return;
2796                 }
2797             }
2798         }
2799 
2800         if (subject().isEmpty()) {
2801             mEdtSubject->setFocus();
2802             const int rc = KMessageBox::questionYesNo(this,
2803                                                       i18n("You did not specify a subject. "
2804                                                            "Send message anyway?"),
2805                                                       i18n("No Subject Specified"),
2806                                                       KGuiItem(i18n("S&end as Is")),
2807                                                       KGuiItem(i18n("&Specify the Subject")));
2808             if (rc == KMessageBox::No) {
2809                 return;
2810             }
2811         }
2812 
2813         MessageComposer::PluginEditorCheckBeforeSendParams params;
2814         params.setSubject(subject());
2815         params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
2816         params.setIdentity(currentIdentity());
2817         params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0);
2818         params.setTransportId(mComposerBase->transportComboBox()->currentTransportId());
2819         const auto ident = identity();
2820         QString defaultDomainName;
2821         if (!ident.isNull()) {
2822             defaultDomainName = ident.defaultDomainName();
2823         }
2824         const QString composerBaseBccTrimmed = mComposerBase->bcc().trimmed();
2825         const QString composerBaseToTrimmed = mComposerBase->to().trimmed();
2826         const QString composerBaseCcTrimmed = mComposerBase->cc().trimmed();
2827         params.setBccAddresses(composerBaseBccTrimmed);
2828         params.setToAddresses(composerBaseToTrimmed);
2829         params.setCcAddresses(composerBaseCcTrimmed);
2830         params.setDefaultDomain(defaultDomainName);
2831 
2832         if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) {
2833             return;
2834         }
2835         const QStringList recipients = {composerBaseToTrimmed, composerBaseCcTrimmed, composerBaseBccTrimmed};
2836 
2837         setEnabled(false);
2838 
2839         // Validate the To:, CC: and BCC fields
2840         auto job = new AddressValidationJob(recipients.join(QLatin1String(", ")), this, this);
2841         job->setDefaultDomain(defaultDomainName);
2842         job->setProperty("method", static_cast<int>(method));
2843         job->setProperty("saveIn", static_cast<int>(saveIn));
2844         connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend);
2845         job->start();
2846 
2847         // we'll call send from within slotDoDelaySend
2848     } else {
2849         if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptAction->isChecked() && KMailSettings::self()->alwaysEncryptDrafts()
2850             && mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) {
2851             KMessageBox::information(this,
2852                                      i18n("You must specify at least one receiver "
2853                                           "in order to be able to encrypt a draft."));
2854             return;
2855         }
2856         doDelayedSend(method, saveIn);
2857     }
2858 }
2859 
slotDoDelayedSend(KJob * job)2860 void KMComposerWin::slotDoDelayedSend(KJob *job)
2861 {
2862     if (job->error()) {
2863         KMessageBox::error(this, job->errorText());
2864         setEnabled(true);
2865         return;
2866     }
2867 
2868     const AddressValidationJob *validateJob = qobject_cast<AddressValidationJob *>(job);
2869 
2870     // Abort sending if one of the recipient addresses is invalid ...
2871     if (!validateJob->isValid()) {
2872         setEnabled(true);
2873         return;
2874     }
2875 
2876     // ... otherwise continue as usual
2877     const MessageComposer::MessageSender::SendMethod method = static_cast<MessageComposer::MessageSender::SendMethod>(job->property("method").toInt());
2878     const MessageComposer::MessageSender::SaveIn saveIn = static_cast<MessageComposer::MessageSender::SaveIn>(job->property("saveIn").toInt());
2879 
2880     doDelayedSend(method, saveIn);
2881 }
2882 
applyComposerSetting(MessageComposer::ComposerViewBase * mComposerBase)2883 void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase)
2884 {
2885     QVector<QByteArray> charsets = mCodecAction->mimeCharsets();
2886     if (!mOriginalPreferredCharset.isEmpty()) {
2887         charsets.insert(0, mOriginalPreferredCharset);
2888     }
2889     mComposerBase->setFrom(from());
2890     mComposerBase->setSubject(subject());
2891     mComposerBase->setCharsets(charsets);
2892     mComposerBase->setUrgent(mUrgentAction->isChecked());
2893     mComposerBase->setMDNRequested(mRequestMDNAction->isChecked());
2894     mComposerBase->setRequestDeleveryConfirmation(mRequestDeliveryConfirmation->isChecked());
2895 }
2896 
doDelayedSend(MessageComposer::MessageSender::SendMethod method,MessageComposer::MessageSender::SaveIn saveIn)2897 void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn)
2898 {
2899     KCursorSaver saver(Qt::WaitCursor);
2900     applyComposerSetting(mComposerBase);
2901     if (mForceDisableHtml) {
2902         disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2903     }
2904     const bool sign = mSignAction->isChecked();
2905     const bool encrypt = mEncryptAction->isChecked();
2906 
2907     mComposerBase->setCryptoOptions(
2908         sign,
2909         encrypt,
2910         cryptoMessageFormat(),
2911         ((saveIn != MessageComposer::MessageSender::SaveInNone && !KMailSettings::self()->alwaysEncryptDrafts()) || mSigningAndEncryptionExplicitlyDisabled));
2912 
2913     const int num = KMailSettings::self()->customMessageHeadersCount();
2914     QMap<QByteArray, QString> customHeader;
2915     for (int ix = 0; ix < num; ++ix) {
2916         CustomMimeHeader customMimeHeader(QString::number(ix));
2917         customMimeHeader.load();
2918         customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue());
2919     }
2920 
2921     QMap<QByteArray, QString>::const_iterator extraCustomHeader = mExtraHeaders.constBegin();
2922     while (extraCustomHeader != mExtraHeaders.constEnd()) {
2923         customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value());
2924         ++extraCustomHeader;
2925     }
2926 
2927     mComposerBase->setCustomHeader(customHeader);
2928     mComposerBase->send(method, saveIn, false);
2929 }
2930 
sendLaterRegistered() const2931 bool KMComposerWin::sendLaterRegistered() const
2932 {
2933     return MessageComposer::SendLaterUtil::sentLaterAgentWasRegistered() && MessageComposer::SendLaterUtil::sentLaterAgentEnabled();
2934 }
2935 
slotSendLater()2936 void KMComposerWin::slotSendLater()
2937 {
2938     if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
2939         return;
2940     }
2941     if (!checkRecipientNumber()) {
2942         return;
2943     }
2944     mComposerBase->setSendLaterInfo(nullptr);
2945     if (mComposerBase->editor()->checkExternalEditorFinished()) {
2946         const bool wasRegistered = sendLaterRegistered();
2947         if (wasRegistered) {
2948             MessageComposer::SendLaterInfo *info = nullptr;
2949             QPointer<MessageComposer::SendLaterDialog> dlg = new MessageComposer::SendLaterDialog(info, this);
2950             if (dlg->exec()) {
2951                 info = dlg->info();
2952                 const MessageComposer::SendLaterDialog::SendLaterAction action = dlg->action();
2953                 delete dlg;
2954                 switch (action) {
2955                 case MessageComposer::SendLaterDialog::Unknown:
2956                     qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it.";
2957                     break;
2958                 case MessageComposer::SendLaterDialog::Canceled:
2959                     return;
2960                     break;
2961                 case MessageComposer::SendLaterDialog::PutInOutbox:
2962                     doSend(MessageComposer::MessageSender::SendLater);
2963                     break;
2964                 case MessageComposer::SendLaterDialog::SendDeliveryAtTime:
2965                     mComposerBase->setSendLaterInfo(info);
2966                     if (info->isRecurrence()) {
2967                         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates, true);
2968                     } else {
2969                         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts, true);
2970                     }
2971                     break;
2972                 }
2973             } else {
2974                 delete dlg;
2975             }
2976         } else {
2977             doSend(MessageComposer::MessageSender::SendLater);
2978         }
2979     }
2980 }
2981 
slotSaveDraft()2982 void KMComposerWin::slotSaveDraft()
2983 {
2984     if (mComposerBase->editor()->checkExternalEditorFinished()) {
2985         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts);
2986     }
2987 }
2988 
slotSaveTemplate()2989 void KMComposerWin::slotSaveTemplate()
2990 {
2991     if (mComposerBase->editor()->checkExternalEditorFinished()) {
2992         doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates);
2993     }
2994 }
2995 
slotSendNowVia(MailTransport::Transport * transport)2996 void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport)
2997 {
2998     if (transport) {
2999         mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3000         slotSendNow();
3001     }
3002 }
3003 
slotSendLaterVia(MailTransport::Transport * transport)3004 void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport)
3005 {
3006     if (transport) {
3007         mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3008         slotSendLater();
3009     }
3010 }
3011 
sendNow(bool shortcutUsed)3012 void KMComposerWin::sendNow(bool shortcutUsed)
3013 {
3014     if (!mComposerBase->editor()->checkExternalEditorFinished()) {
3015         return;
3016     }
3017     if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
3018         return;
3019     }
3020     if (!checkRecipientNumber()) {
3021         return;
3022     }
3023     mSendNowByShortcutUsed = shortcutUsed;
3024     if (KMailSettings::self()->checkSpellingBeforeSend()) {
3025         mComposerBase->editor()->forceSpellChecking();
3026     } else {
3027         slotCheckSendNow();
3028     }
3029 }
3030 
slotSendNowByShortcut()3031 void KMComposerWin::slotSendNowByShortcut()
3032 {
3033     sendNow(true);
3034 }
3035 
slotSendNow()3036 void KMComposerWin::slotSendNow()
3037 {
3038     sendNow(false);
3039 }
3040 
confirmBeforeSend()3041 void KMComposerWin::confirmBeforeSend()
3042 {
3043     const int rc = KMessageBox::warningYesNoCancel(mMainWidget,
3044                                                    i18n("About to send email..."),
3045                                                    i18n("Send Confirmation"),
3046                                                    KGuiItem(i18n("&Send Now")),
3047                                                    KGuiItem(i18n("Send &Later")));
3048 
3049     if (rc == KMessageBox::Yes) {
3050         doSend(MessageComposer::MessageSender::SendImmediate);
3051     } else if (rc == KMessageBox::No) {
3052         doSend(MessageComposer::MessageSender::SendLater);
3053     }
3054 }
3055 
slotCheckSendNowStep2()3056 void KMComposerWin::slotCheckSendNowStep2()
3057 {
3058     if (KMailSettings::self()->confirmBeforeSend()) {
3059         confirmBeforeSend();
3060     } else {
3061         if (mSendNowByShortcutUsed) {
3062             if (!KMailSettings::self()->checkSendDefaultActionShortcut()) {
3063                 ValidateSendMailShortcut validateShortcut(actionCollection(), this);
3064                 if (!validateShortcut.validate()) {
3065                     return;
3066                 }
3067             }
3068             if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) {
3069                 confirmBeforeSend();
3070                 return;
3071             }
3072         }
3073         doSend(MessageComposer::MessageSender::SendImmediate);
3074     }
3075 }
3076 
slotDelayedCheckSendNow()3077 void KMComposerWin::slotDelayedCheckSendNow()
3078 {
3079     QTimer::singleShot(0, this, &KMComposerWin::slotCheckSendNow);
3080 }
3081 
slotCheckSendNow()3082 void KMComposerWin::slotCheckSendNow()
3083 {
3084     QStringList lst{mComposerBase->to()};
3085     const QString ccStr = mComposerBase->cc();
3086     if (!ccStr.isEmpty()) {
3087         lst << KEmailAddress::splitAddressList(ccStr);
3088     }
3089     const QString bccStr = mComposerBase->bcc();
3090     if (!bccStr.isEmpty()) {
3091         lst << KEmailAddress::splitAddressList(bccStr);
3092     }
3093     if (lst.isEmpty()) {
3094         slotCheckSendNowStep2();
3095     } else {
3096         auto job = new PotentialPhishingEmailJob(this);
3097         KConfigGroup group(KSharedConfig::openConfig(), "PotentialPhishing");
3098         const QStringList whiteList = group.readEntry("whiteList", QStringList());
3099         job->setEmailWhiteList(whiteList);
3100         job->setPotentialPhishingEmails(lst);
3101         connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound);
3102         if (!job->start()) {
3103             qCWarning(KMAIL_LOG) << "PotentialPhishingEmailJob can't start";
3104         }
3105     }
3106 }
3107 
slotPotentialPhishingEmailsFound(const QStringList & list)3108 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list)
3109 {
3110     if (list.isEmpty()) {
3111         slotCheckSendNowStep2();
3112     } else {
3113         mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list);
3114     }
3115 }
3116 
checkRecipientNumber() const3117 bool KMComposerWin::checkRecipientNumber() const
3118 {
3119     const int thresHold = KMailSettings::self()->recipientThreshold();
3120     if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) {
3121         if (KMessageBox::questionYesNo(mMainWidget,
3122                                        i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold),
3123                                        i18n("Too many recipients"),
3124                                        KGuiItem(i18n("&Send as Is")),
3125                                        KGuiItem(i18n("&Edit Recipients")))
3126             == KMessageBox::No) {
3127             return false;
3128         }
3129     }
3130     return true;
3131 }
3132 
enableHtml()3133 void KMComposerWin::enableHtml()
3134 {
3135     if (mForceDisableHtml) {
3136         disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
3137         return;
3138     }
3139 
3140     mComposerBase->editor()->activateRichText();
3141     if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
3142         // Use singleshot, as we we might actually be called from a slot that wanted to disable the
3143         // toolbar (but the messagebox in disableHtml() prevented that and called us).
3144         // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
3145         // signal, so wait one event loop run for that.
3146         QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show);
3147     }
3148     if (!mMarkupAction->isChecked()) {
3149         mMarkupAction->setChecked(true);
3150     }
3151 
3152     mComposerBase->editor()->composerActions()->updateActionStates();
3153     mComposerBase->editor()->composerActions()->setActionsEnabled(true);
3154 }
3155 
disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation)3156 void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation)
3157 {
3158     bool forcePlainTextMarkup = false;
3159     if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed()
3160         && !mForceDisableHtml) {
3161         int choice = KMessageBox::warningYesNoCancel(this,
3162                                                      i18n("Turning HTML mode off "
3163                                                           "will cause the text to lose the formatting. Are you sure?"),
3164                                                      i18n("Lose the formatting?"),
3165                                                      KGuiItem(i18n("Lose Formatting")),
3166                                                      KGuiItem(i18n("Add Markup Plain Text")),
3167                                                      KStandardGuiItem::cancel(),
3168                                                      QStringLiteral("LoseFormattingWarning"));
3169 
3170         switch (choice) {
3171         case KMessageBox::Cancel:
3172             enableHtml();
3173             return;
3174         case KMessageBox::No:
3175             forcePlainTextMarkup = true;
3176             break;
3177         case KMessageBox::Yes:
3178             break;
3179         }
3180     }
3181 
3182     mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup);
3183     mComposerBase->editor()->switchToPlainText();
3184     mComposerBase->editor()->composerActions()->setActionsEnabled(false);
3185 
3186     slotUpdateFont();
3187     if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
3188         // See the comment in enableHtml() why we use a singleshot timer, similar situation here.
3189         QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide);
3190     }
3191     if (mMarkupAction->isChecked()) {
3192         mMarkupAction->setChecked(false);
3193     }
3194 }
3195 
slotToggleMarkup()3196 void KMComposerWin::slotToggleMarkup()
3197 {
3198     htmlToolBarVisibilityChanged(mMarkupAction->isChecked());
3199 }
3200 
slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode)3201 void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode)
3202 {
3203     if (mode == MessageComposer::RichTextComposerNg::Plain) {
3204         disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all?
3205     } else {
3206         enableHtml();
3207     }
3208     enableDisablePluginActions(mode == MessageComposer::RichTextComposerNg::Rich);
3209 }
3210 
enableDisablePluginActions(bool richText)3211 void KMComposerWin::enableDisablePluginActions(bool richText)
3212 {
3213     mPluginEditorConvertTextManagerInterface->enableDisablePluginActions(richText);
3214 }
3215 
htmlToolBarVisibilityChanged(bool visible)3216 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible)
3217 {
3218     if (visible) {
3219         enableHtml();
3220     } else {
3221         disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
3222     }
3223 }
3224 
slotAutoSpellCheckingToggled(bool on)3225 void KMComposerWin::slotAutoSpellCheckingToggled(bool on)
3226 {
3227     mAutoSpellCheckingAction->setChecked(on);
3228     if (on != mComposerBase->editor()->checkSpellingEnabled()) {
3229         mComposerBase->editor()->setCheckSpellingEnabled(on);
3230     }
3231     if (on != mEdtSubject->checkSpellingEnabled()) {
3232         mEdtSubject->setCheckSpellingEnabled(on);
3233     }
3234     mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on);
3235 }
3236 
showAndActivateComposer()3237 void KMComposerWin::showAndActivateComposer()
3238 {
3239     show();
3240     raise();
3241     activateWindow();
3242 }
3243 
slotSpellCheckingStatus(const QString & status)3244 void KMComposerWin::slotSpellCheckingStatus(const QString &status)
3245 {
3246     mStatusbarLabel->setText(status);
3247     QTimer::singleShot(2s, this, &KMComposerWin::slotSpellcheckDoneClearStatus);
3248 }
3249 
slotSpellcheckDoneClearStatus()3250 void KMComposerWin::slotSpellcheckDoneClearStatus()
3251 {
3252     mStatusbarLabel->clear();
3253 }
3254 
slotIdentityChanged(uint uoid,bool initialChange)3255 void KMComposerWin::slotIdentityChanged(uint uoid, bool initialChange)
3256 {
3257     if (!mMsg) {
3258         qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!";
3259         return;
3260     }
3261     const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(uoid);
3262     if (ident.isNull()) {
3263         return;
3264     }
3265     const bool wasModified(isModified());
3266     Q_EMIT identityChanged(identity());
3267     if (!ident.fullEmailAddr().isNull()) {
3268         mEdtFrom->setText(ident.fullEmailAddr());
3269     }
3270 
3271     // make sure the From field is shown if it does not contain a valid email address
3272     if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
3273         mShowHeaders |= HDR_FROM;
3274     }
3275 
3276     // remove BCC of old identity and add BCC of new identity (if they differ)
3277     const KIdentityManagement::Identity &oldIdentity = KMKernel::self()->identityManager()->identityForUoidOrDefault(mId);
3278 
3279     if (ident.organization().isEmpty()) {
3280         mMsg->removeHeader<KMime::Headers::Organization>();
3281     } else {
3282         auto const organization = new KMime::Headers::Organization;
3283         organization->fromUnicodeString(ident.organization(), "utf-8");
3284         mMsg->setHeader(organization);
3285     }
3286 
3287     addFaceHeaders(ident, mMsg);
3288 
3289     if (initialChange) {
3290         if (auto hrd = mMsg->headerByType("X-KMail-Transport")) {
3291             const QString mailtransportStr = hrd->asUnicodeString();
3292             if (!mailtransportStr.isEmpty()) {
3293                 int transportId = mailtransportStr.toInt();
3294                 const Transport *transport = TransportManager::self()->transportById(transportId, false); /*don't return default transport */
3295                 if (transport) {
3296                     auto header = new KMime::Headers::Generic("X-KMail-Transport");
3297                     header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3298                     mMsg->setHeader(header);
3299                     mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3300                 } else {
3301                     if (auto hrd = mMsg->headerByType("X-KMail-Transport-Name")) {
3302                         const QString identityStrName = hrd->asUnicodeString();
3303                         const Transport *transport = TransportManager::self()->transportByName(identityStrName, true);
3304                         if (transport) {
3305                             auto header = new KMime::Headers::Generic("X-KMail-Transport");
3306                             header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3307                             mMsg->setHeader(header);
3308                             mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3309                         } else {
3310                             mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3311                         }
3312                     } else {
3313                         mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3314                     }
3315                 }
3316             }
3317         } else {
3318             const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
3319             const Transport *transport = TransportManager::self()->transportById(transportId, true);
3320             if (transport) {
3321                 auto header = new KMime::Headers::Generic("X-KMail-Transport");
3322                 header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3323                 mMsg->setHeader(header);
3324                 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3325             } else {
3326                 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3327             }
3328         }
3329     } else {
3330         const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
3331         const Transport *transport = TransportManager::self()->transportById(transportId, true);
3332         if (!transport) {
3333             mMsg->removeHeader("X-KMail-Transport");
3334             mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
3335         } else {
3336             auto header = new KMime::Headers::Generic("X-KMail-Transport");
3337             header->fromUnicodeString(QString::number(transport->id()), "utf-8");
3338             mMsg->setHeader(header);
3339             mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
3340         }
3341     }
3342 
3343     const bool fccIsDisabled = ident.disabledFcc();
3344     if (fccIsDisabled) {
3345         auto header = new KMime::Headers::Generic("X-KMail-FccDisabled");
3346         header->fromUnicodeString(QStringLiteral("true"), "utf-8");
3347         mMsg->setHeader(header);
3348     } else {
3349         mMsg->removeHeader("X-KMail-FccDisabled");
3350     }
3351     mFccFolder->setEnabled(!fccIsDisabled);
3352 
3353     mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
3354     slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary());
3355     if (!mPreventFccOverwrite) {
3356         setFcc(ident.fcc());
3357     }
3358     // if unmodified, apply new template, if one is set
3359     if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) && !initialChange) {
3360         applyTemplate(uoid, mId, ident, wasModified);
3361     } else {
3362         mComposerBase->identityChanged(ident, oldIdentity, false);
3363         mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage());
3364         updateComposerAfterIdentityChanged(ident, uoid, wasModified);
3365     }
3366 }
3367 
updateComposerAfterIdentityChanged(const KIdentityManagement::Identity & ident,uint uoid,bool wasModified)3368 void KMComposerWin::updateComposerAfterIdentityChanged(const KIdentityManagement::Identity &ident, uint uoid, bool wasModified)
3369 {
3370     // disable certain actions if there is no PGP user identity set
3371     // for this profile
3372     bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
3373     bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
3374     // save the state of the sign and encrypt button
3375     if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) {
3376         mLastEncryptActionState = mEncryptAction->isChecked();
3377         setEncryption(false);
3378     }
3379     if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) {
3380         mLastSignActionState = mSignAction->isChecked();
3381         setSigning(false);
3382     }
3383     // restore the last state of the sign and encrypt button
3384     if (bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey) {
3385         setEncryption(mLastEncryptActionState);
3386     }
3387     if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) {
3388         setSigning(mLastSignActionState);
3389     }
3390 
3391     mCryptoModuleAction->setCurrentItem(format2cb(Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat())));
3392     slotSelectCryptoModule(true);
3393 
3394     mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
3395     mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
3396     const KIdentityManagement::Signature sig = const_cast<KIdentityManagement::Identity &>(ident).signature();
3397     bool isEnabledSignature = sig.isEnabledSignature();
3398     mAppendSignature->setEnabled(isEnabledSignature);
3399     mPrependSignature->setEnabled(isEnabledSignature);
3400     mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature);
3401 
3402     mId = uoid;
3403     changeCryptoAction();
3404     // make sure the From and BCC fields are shown if necessary
3405     rethinkFields(false);
3406     setModified(wasModified);
3407 
3408     // Update encryption status of all recipients, if encryption state is not set by user
3409     const bool setByUser = mEncryptAction->property("setByUser").toBool();
3410     if (!setByUser && ident.pgpAutoEncrypt()) {
3411         const auto lst = mComposerBase->recipientsEditor()->lines();
3412         for (auto line : lst) {
3413             slotRecipientAdded(qobject_cast<MessageComposer::RecipientLineNG *>(line));
3414         }
3415     }
3416 }
3417 
slotSpellcheckConfig()3418 void KMComposerWin::slotSpellcheckConfig()
3419 {
3420     static_cast<KMComposerEditorNg *>(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc"));
3421 }
3422 
slotEditToolbars()3423 void KMComposerWin::slotEditToolbars()
3424 {
3425     QPointer<KEditToolBar> dlg = new KEditToolBar(guiFactory(), this);
3426 
3427     connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars);
3428 
3429     dlg->exec();
3430     delete dlg;
3431 }
3432 
slotUpdateToolbars()3433 void KMComposerWin::slotUpdateToolbars()
3434 {
3435     createGUI(QStringLiteral("kmcomposerui.rc"));
3436     applyMainWindowSettings(KMKernel::self()->config()->group("Composer"));
3437 }
3438 
slotEditKeys()3439 void KMComposerWin::slotEditKeys()
3440 {
3441     KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this);
3442 }
3443 
setFocusToEditor()3444 void KMComposerWin::setFocusToEditor()
3445 {
3446     // The cursor position is already set by setMsg(), so we only need to set the
3447     // focus here.
3448     mComposerBase->editor()->setFocus();
3449 }
3450 
setFocusToSubject()3451 void KMComposerWin::setFocusToSubject()
3452 {
3453     mEdtSubject->setFocus();
3454 }
3455 
slotCompletionModeChanged(KCompletion::CompletionMode mode)3456 void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode)
3457 {
3458     KMailSettings::self()->setCompletionMode(static_cast<int>(mode));
3459 
3460     // sync all the lineedits to the same completion mode
3461     mEdtFrom->setCompletionMode(mode);
3462     mComposerBase->recipientsEditor()->setCompletionMode(mode);
3463 }
3464 
slotConfigChanged()3465 void KMComposerWin::slotConfigChanged()
3466 {
3467     readConfig(true /*reload*/);
3468     mComposerBase->updateAutoSave();
3469     rethinkFields();
3470     slotWordWrapToggled(mWordWrapAction->isChecked());
3471 }
3472 
3473 /*
3474  * checks if the drafts-folder has been deleted
3475  * that is not nice so we set the system-drafts-folder
3476  */
slotFolderRemoved(const Akonadi::Collection & col)3477 void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col)
3478 {
3479     qCDebug(KMAIL_LOG) << "you killed me.";
3480     // TODO: need to handle templates here?
3481     if ((mFolder.isValid()) && (col.id() == mFolder.id())) {
3482         mFolder = CommonKernel->draftsCollectionFolder();
3483         qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id();
3484     } else if (col.id() == mFccFolder->collection().id()) {
3485         qCDebug(KMAIL_LOG) << "FCC was removed " << col.id();
3486         mFccFolder->setCollection(CommonKernel->sentCollectionFolder());
3487         mIncorrectIdentityFolderWarning->fccIsInvalid();
3488     }
3489 }
3490 
slotOverwriteModeChanged()3491 void KMComposerWin::slotOverwriteModeChanged()
3492 {
3493     const bool overwriteMode = mComposerBase->editor()->overwriteMode();
3494     mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1);
3495     mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode);
3496 }
3497 
slotCursorPositionChanged()3498 void KMComposerWin::slotCursorPositionChanged()
3499 {
3500     // Change Line/Column info in status bar
3501     const int line = mComposerBase->editor()->linePosition() + 1;
3502     const int col = mComposerBase->editor()->columnNumber() + 1;
3503     QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line);
3504     mCursorLineLabel->setText(temp);
3505     temp = i18n(" Column: %1 ", col);
3506     mCursorColumnLabel->setText(temp);
3507 
3508     // Show link target in status bar
3509     if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) {
3510         const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1String(" -> ")
3511             + mComposerBase->editor()->composerControler()->currentLinkUrl();
3512         mStatusbarLabel->setText(text);
3513     } else {
3514         mStatusbarLabel->clear();
3515     }
3516 }
3517 
recipientEditorSizeHintChanged()3518 void KMComposerWin::recipientEditorSizeHintChanged()
3519 {
3520     QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize);
3521 }
3522 
setMaximumHeaderSize()3523 void KMComposerWin::setMaximumHeaderSize()
3524 {
3525     mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
3526 }
3527 
updateSignatureAndEncryptionStateIndicators()3528 void KMComposerWin::updateSignatureAndEncryptionStateIndicators()
3529 {
3530     mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(mSignAction->isChecked(), mEncryptAction->isChecked());
3531 }
3532 
slotDictionaryLanguageChanged(const QString & language)3533 void KMComposerWin::slotDictionaryLanguageChanged(const QString &language)
3534 {
3535     mComposerBase->dictionary()->setCurrentByDictionary(language);
3536 }
3537 
slotFccFolderChanged(const Akonadi::Collection & collection)3538 void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection)
3539 {
3540     mComposerBase->setFcc(collection);
3541     mComposerBase->editor()->document()->setModified(true);
3542 }
3543 
slotSaveAsFile()3544 void KMComposerWin::slotSaveAsFile()
3545 {
3546     auto job = new SaveAsFileJob(this);
3547     job->setParentWidget(this);
3548     job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
3549     job->setTextDocument(mComposerBase->editor()->document());
3550     job->start();
3551     // not necessary to delete it. It done in SaveAsFileJob
3552 }
3553 
slotAttachMissingFile()3554 void KMComposerWin::slotAttachMissingFile()
3555 {
3556     mComposerBase->attachmentController()->showAddAttachmentFileDialog();
3557 }
3558 
slotVerifyMissingAttachmentTimeout()3559 void KMComposerWin::slotVerifyMissingAttachmentTimeout()
3560 {
3561     if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) {
3562         mAttachmentMissing->animatedShow();
3563     }
3564 }
3565 
slotExplicitClosedMissingAttachment()3566 void KMComposerWin::slotExplicitClosedMissingAttachment()
3567 {
3568     if (mVerifyMissingAttachment) {
3569         mVerifyMissingAttachment->stop();
3570         delete mVerifyMissingAttachment;
3571         mVerifyMissingAttachment = nullptr;
3572     }
3573 }
3574 
addExtraCustomHeaders(const QMap<QByteArray,QString> & headers)3575 void KMComposerWin::addExtraCustomHeaders(const QMap<QByteArray, QString> &headers)
3576 {
3577     mExtraHeaders = headers;
3578 }
3579 
processModifyText(QKeyEvent * event)3580 bool KMComposerWin::processModifyText(QKeyEvent *event)
3581 {
3582     return mPluginEditorManagerInterface->processProcessKeyEvent(event);
3583 }
3584 
convertPlainText(MessageComposer::TextPart * textPart)3585 MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus KMComposerWin::convertPlainText(MessageComposer::TextPart *textPart)
3586 {
3587     return mPluginEditorConvertTextManagerInterface->convertTextToFormat(textPart);
3588 }
3589 
slotExternalEditorStarted()3590 void KMComposerWin::slotExternalEditorStarted()
3591 {
3592     mComposerBase->identityCombo()->setEnabled(false);
3593     mExternalEditorWarning->show();
3594 }
3595 
slotExternalEditorClosed()3596 void KMComposerWin::slotExternalEditorClosed()
3597 {
3598     mComposerBase->identityCombo()->setEnabled(true);
3599     mExternalEditorWarning->hide();
3600 }
3601 
slotInsertShortUrl(const QString & url)3602 void KMComposerWin::slotInsertShortUrl(const QString &url)
3603 {
3604     mComposerBase->editor()->composerControler()->insertLink(url);
3605 }
3606 
slotTransportChanged()3607 void KMComposerWin::slotTransportChanged()
3608 {
3609     mComposerBase->editor()->document()->setModified(true);
3610 }
3611 
slotFollowUpMail(bool toggled)3612 void KMComposerWin::slotFollowUpMail(bool toggled)
3613 {
3614     if (toggled) {
3615         QPointer<MessageComposer::FollowUpReminderSelectDateDialog> dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this);
3616         if (dlg->exec()) {
3617             mComposerBase->setFollowUpDate(dlg->selectedDate());
3618             mComposerBase->setFollowUpCollection(dlg->collection());
3619         } else {
3620             mFollowUpToggleAction->setChecked(false);
3621         }
3622         delete dlg;
3623     } else {
3624         mComposerBase->clearFollowUp();
3625     }
3626 }
3627 
slotSnippetWidgetVisibilityChanged(bool b)3628 void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b)
3629 {
3630     mSnippetWidget->setVisible(b);
3631     mSnippetSplitterCollapser->setVisible(b);
3632 }
3633 
slotOverwriteModeWasChanged(bool state)3634 void KMComposerWin::slotOverwriteModeWasChanged(bool state)
3635 {
3636     mComposerBase->editor()->setCursorWidth(state ? 5 : 1);
3637     mComposerBase->editor()->setOverwriteMode(state);
3638 }
3639 
customToolsList() const3640 QList<KToggleAction *> KMComposerWin::customToolsList() const
3641 {
3642     return mCustomToolsWidget->actionList();
3643 }
3644 
pluginToolsActionListForPopupMenu() const3645 QList<QAction *> KMComposerWin::pluginToolsActionListForPopupMenu() const
3646 {
3647     return mPluginEditorManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu)
3648         + mPluginEditorConvertTextManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu);
3649 }
3650 
slotRecipientEditorLineAdded(KPIM::MultiplyingLine * line_)3651 void KMComposerWin::slotRecipientEditorLineAdded(KPIM::MultiplyingLine *line_)
3652 {
3653     auto line = qobject_cast<MessageComposer::RecipientLineNG *>(line_);
3654     Q_ASSERT(line);
3655 
3656     connect(line, &MessageComposer::RecipientLineNG::countChanged, this, [this, line]() {
3657         this->slotRecipientAdded(line);
3658     });
3659     connect(line, &MessageComposer::RecipientLineNG::iconClicked, this, [this, line]() {
3660         this->slotRecipientLineIconClicked(line);
3661     });
3662     connect(line, &MessageComposer::RecipientLineNG::destroyed, this, &KMComposerWin::slotRecipientEditorFocusChanged, Qt::QueuedConnection);
3663     connect(line, &MessageComposer::RecipientLineNG::activeChanged, this, [this, line]() {
3664         this->slotRecipientFocusLost(line);
3665     });
3666 
3667     slotRecipientEditorFocusChanged();
3668 }
3669 
slotRecipientEditorFocusChanged()3670 void KMComposerWin::slotRecipientEditorFocusChanged()
3671 {
3672     // Already disabled
3673     if (mEncryptAction->property("setByUser").toBool()) {
3674         return;
3675     }
3676 
3677     // Identity has no encryption key so no encryption is possible.
3678     if (identity().pgpEncryptionKey().isEmpty()) {
3679         setEncryption(false, false);
3680         return;
3681     }
3682 
3683     // Focus changed, which basically means that user "committed" a new recipient.
3684     // If we have at least one recipient that does not have a key, disable encryption
3685     // (unless user enabled it manually), because we want to encrypt by default,
3686     // but not by force
3687     bool encrypt = false;
3688     const auto lst = mComposerBase->recipientsEditor()->lines();
3689     for (auto line_ : lst) {
3690         auto line = qobject_cast<MessageComposer::RecipientLineNG *>(line_);
3691 
3692         // There's still a lookup job running, so wait, slotKeyForMailBoxResult()
3693         // will call us if the job returns empty key
3694         if (line->property("keyLookupJob").isValid()) {
3695             return;
3696         }
3697 
3698         const auto keyStatus = static_cast<CryptoKeyState>(line->property("keyStatus").toInt());
3699         if (keyStatus == NoState) {
3700             continue;
3701         }
3702 
3703         if (!line->recipient()->isEmpty() && keyStatus != KeyOk) {
3704             setEncryption(false, false);
3705             return;
3706         }
3707 
3708         encrypt = true;
3709     }
3710 
3711     if (encrypt) {
3712         setEncryption(true, false);
3713     }
3714 }
3715 
slotRecipientLineIconClicked(MessageComposer::RecipientLineNG * line)3716 void KMComposerWin::slotRecipientLineIconClicked(MessageComposer::RecipientLineNG *line)
3717 {
3718     const auto data = line->data().dynamicCast<MessageComposer::Recipient>();
3719 
3720     if (!data->key().isNull()) {
3721         const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
3722         if (exec.isEmpty()
3723             || !QProcess::startDetached(exec,
3724                                         {QStringLiteral("--query"),
3725                                          QString::fromLatin1(data->key().primaryFingerprint()),
3726                                          QStringLiteral("--parent-windowid"),
3727                                          QString::number(winId())})) {
3728             qCWarning(KMAIL_LOG) << "Unable to execute kleopatra";
3729         }
3730     }
3731 }
3732 
slotRecipientAdded(MessageComposer::RecipientLineNG * line)3733 void KMComposerWin::slotRecipientAdded(MessageComposer::RecipientLineNG *line)
3734 {
3735     // User has disabled encryption, don't bother checking the key...
3736     if (!mEncryptAction->isChecked() && mEncryptAction->property("setByUser").toBool()) {
3737         return;
3738     }
3739 
3740     // Same if auto-encryption is not enabled in current identity settings
3741     if (!identity().pgpAutoEncrypt() || identity().pgpEncryptionKey().isEmpty()) {
3742         return;
3743     }
3744 
3745     if (line->recipientsCount() == 0) {
3746         return;
3747     }
3748 
3749     const auto protocol = QGpgME::openpgp();
3750     // If we don't have gnupg we can't look for keys
3751     if (!protocol) {
3752         return;
3753     }
3754 
3755     auto recipient = line->data().dynamicCast<MessageComposer::Recipient>();
3756     // check if is an already running key lookup job and if so, cancel it
3757     // this is to prevent a slower job overwriting results of the job that we
3758     // are about to start now.
3759     const auto runningJob = line->property("keyLookupJob").value<QPointer<QGpgME::KeyForMailboxJob>>();
3760     if (runningJob) {
3761         disconnect(runningJob.data(), &QGpgME::KeyForMailboxJob::result, this, &KMComposerWin::slotKeyForMailBoxResult);
3762         runningJob->slotCancel();
3763         line->setProperty("keyLookupJob", QVariant());
3764     }
3765 
3766     QGpgME::KeyForMailboxJob *job = protocol->keyForMailboxJob();
3767     if (!job) {
3768         line->setProperty("keyStatus", NoKey);
3769         recipient->setEncryptionAction(Kleo::Impossible);
3770         return;
3771     }
3772 
3773     QString dummy;
3774     QString addrSpec;
3775     if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
3776         addrSpec = recipient->email();
3777     }
3778     line->setProperty("keyLookupJob", QVariant::fromValue(QPointer<QGpgME::KeyForMailboxJob>(job)));
3779     job->setProperty("recipient", QVariant::fromValue(recipient));
3780     job->setProperty("line", QVariant::fromValue(QPointer<MessageComposer::RecipientLineNG>(line)));
3781     connect(job, &QGpgME::KeyForMailboxJob::result, this, &KMComposerWin::slotKeyForMailBoxResult);
3782     job->start(addrSpec, true);
3783 }
3784 
slotRecipientFocusLost(MessageComposer::RecipientLineNG * line)3785 void KMComposerWin::slotRecipientFocusLost(MessageComposer::RecipientLineNG *line)
3786 {
3787     if (mEncryptAction->property("setByUser").toBool()) {
3788         return;
3789     }
3790 
3791     // Same if auto-encryption is not enabled in current identity settings
3792     if (!identity().pgpAutoEncrypt() || identity().pgpEncryptionKey().isEmpty()) {
3793         return;
3794     }
3795 
3796     if (line->recipientsCount() == 0) {
3797         return;
3798     }
3799 
3800     if (line->property("keyLookupJob").toBool()) {
3801         return;
3802     }
3803 
3804     if (static_cast<CryptoKeyState>(line->property("keyStatus").toInt()) != KeyOk) {
3805         line->setProperty("keyStatus", NoKey);
3806         setEncryption(false, false);
3807     }
3808 }
3809 
slotKeyForMailBoxResult(const GpgME::KeyListResult &,const GpgME::Key & key,const GpgME::UserID & userID)3810 void KMComposerWin::slotKeyForMailBoxResult(const GpgME::KeyListResult &, const GpgME::Key &key, const GpgME::UserID &userID)
3811 {
3812     QObject *job = sender();
3813     Q_ASSERT(job);
3814 
3815     // Check if the encryption was explicitly disabled while the job was running
3816     if (!mEncryptAction->isChecked() && mEncryptAction->property("setByUser").toBool()) {
3817         return;
3818     }
3819 
3820     auto recipient = job->property("recipient").value<MessageComposer::Recipient::Ptr>();
3821     auto line = job->property("line").value<QPointer<MessageComposer::RecipientLineNG>>();
3822 
3823     if (!recipient || !line) {
3824         return;
3825     }
3826 
3827     line->setProperty("keyLookupJob", QVariant());
3828     if (key.isNull() && identity().autocryptEnabled()) {
3829         const QIcon icon = QIcon::fromTheme(QStringLiteral("gpg"));
3830         QIcon overlay = QIcon::fromTheme(QStringLiteral("emblem-information"));
3831         QString tooltip;
3832 
3833         QString dummy;
3834         QString addrSpec;
3835         if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
3836             addrSpec = recipient->email();
3837         }
3838         const auto storage = MessageCore::AutocryptStorage::self();
3839         const auto rec = storage->getRecipient(addrSpec.toUtf8());
3840         GpgME::Key autocryptKey;
3841         if (rec) {
3842             const auto key = rec->gpgKey();
3843             if (!key.isNull() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && key.canEncrypt()) {
3844                 autocryptKey = key;
3845                 if (rec->prefer_encrypt()) {
3846                     overlay = QIcon::fromTheme(QStringLiteral("emblem-success"));
3847                     tooltip = i18n("Autocrypt key is used for this recipient. This key is not verified. "
3848                                    "The recipient prefers encrypted replies.");
3849                 } else {
3850                     tooltip = i18n("Autocrypt key is used for this recipient. This key is not verified. "
3851                                    "The recipient does not prefer encrypted replies.");
3852                 }
3853             } else {
3854                 const auto gossipKey = rec->gossipKey();
3855                 if (!gossipKey.isNull() && !gossipKey.isRevoked() && !gossipKey.isExpired() && !gossipKey.isDisabled() && gossipKey.canEncrypt()) {
3856                     autocryptKey = gossipKey;
3857                     tooltip = i18n("Autocrypt gossip key is used for this recipient. This key is not verified.");
3858                 }
3859             }
3860         }
3861 
3862         if (!autocryptKey.isNull()) {
3863             recipient->setEncryptionAction(Kleo::DoIt);
3864             recipient->setKey(autocryptKey);
3865             line->setProperty("keyStatus", KeyOk);
3866             line->setIcon(KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner), tooltip);
3867 
3868             slotRecipientEditorFocusChanged();
3869         } else {
3870             recipient->setEncryptionAction(Kleo::Impossible); // no key
3871             line->setIcon(QIcon());
3872             line->setProperty("keyStatus", InProgress);
3873         }
3874     } else if(key.isNull()) {
3875         recipient->setEncryptionAction(Kleo::Impossible); // no key
3876         line->setIcon(QIcon());
3877         line->setProperty("keyStatus", InProgress);
3878     } else {
3879         recipient->setEncryptionAction(Kleo::DoIt);
3880         recipient->setKey(key);
3881 
3882         const QIcon icon = QIcon::fromTheme(QStringLiteral("gpg"));
3883         QIcon overlay;
3884         QString tooltip;
3885         switch (userID.validity()) {
3886         case GpgME::UserID::Ultimate:
3887         case GpgME::UserID::Full:
3888             overlay = QIcon::fromTheme(QStringLiteral("emblem-favorite"));
3889             tooltip = i18n(
3890                 "High security encryption will be used for this recipient (the encryption key is fully trusted). "
3891                 "Click the icon for details.");
3892             break;
3893         case GpgME::UserID::Marginal:
3894             overlay = QIcon::fromTheme(QStringLiteral("emblem-success"));
3895             tooltip = i18n(
3896                 "Medium security encryption will be used for this recipient (the encryption key is marginally trusted). "
3897                 "Click the icon for details.");
3898             break;
3899         case GpgME::UserID::Never:
3900             overlay = QIcon::fromTheme(QStringLiteral("emblem-error"));
3901             tooltip = i18n(
3902                 "Low security encryption will be used for this recipient (the encryption key is untrusted). "
3903                 "Click the icon for details.");
3904             break;
3905         case GpgME::UserID::Undefined:
3906         case GpgME::UserID::Unknown:
3907             overlay = QIcon::fromTheme(QStringLiteral("emblem-information"));
3908             tooltip = i18n(
3909                 "The email to this recipient will be encrypted, but the security of the encryption is unknown "
3910                 "(the encryption key could not be verified). Click the icon for details.");
3911             break;
3912         }
3913 
3914         line->setProperty("keyStatus", KeyOk);
3915         line->setIcon(KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner), tooltip);
3916 
3917         slotRecipientEditorFocusChanged();
3918     }
3919 }
3920 
slotIdentityDeleted(uint uoid)3921 void KMComposerWin::slotIdentityDeleted(uint uoid)
3922 {
3923     if (currentIdentity() == uoid) {
3924         mIncorrectIdentityFolderWarning->identityInvalid();
3925     }
3926 }
3927 
slotTransportRemoved(int id,const QString & name)3928 void KMComposerWin::slotTransportRemoved(int id, const QString &name)
3929 {
3930     Q_UNUSED(name)
3931     if (mComposerBase->transportComboBox()->currentTransportId() == id) {
3932         mIncorrectIdentityFolderWarning->mailTransportIsInvalid();
3933     }
3934 }
3935 
slotSelectionChanged()3936 void KMComposerWin::slotSelectionChanged()
3937 {
3938     Q_EMIT mPluginEditorManagerInterface->textSelectionChanged(mRichTextEditorwidget->editor()->textCursor().hasSelection());
3939 }
3940 
slotMessage(const QString & str)3941 void KMComposerWin::slotMessage(const QString &str)
3942 {
3943     KMessageBox::information(this, str, i18n("Plugin Editor Information"));
3944 }
3945 
slotEditorPluginInsertText(const QString & str)3946 void KMComposerWin::slotEditorPluginInsertText(const QString &str)
3947 {
3948     mGlobalAction->slotInsertText(str);
3949 }
3950