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