Merge remote-tracking branch 'origin/Applications/16.08
[kdepim.git] / kmail / src / editor / kmcomposerwin.cpp
blob6716aa35a2cc8203999057532abb3b226dc2ff00
1 /*
2 * This file is part of KMail.
3 * Copyright (c) 2011-2016 Laurent Montel <montel@kde.org>
5 * Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
7 * Based on KMail code by:
8 * Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 #include "kmcomposerwin.h"
26 // KMail includes
27 #include "job/addressvalidationjob.h"
28 #include "attachment/attachmentcontroller.h"
29 #include <MessageComposer/AttachmentModel>
30 #include "attachment/attachmentview.h"
31 #include "codec/codecaction.h"
32 #include "MessageComposer/Kleo_Util"
33 #include "kmcommands.h"
34 #include "editor/kmcomposereditorng.h"
35 #include "KPIMTextEdit/RichTextComposerControler"
36 #include "MessageComposer/RichTextComposerSignatures"
37 #include <KPIMTextEdit/RichTextComposerActions>
38 #include <KPIMTextEdit/RichTextComposerImages>
39 #include <KPIMTextEdit/RichTextExternalComposer>
40 #include <KPIMTextEdit/RichTextEditorWidget>
41 #include "kmkernel.h"
42 #include "settings/kmailsettings.h"
43 #include "kmmainwin.h"
44 #include "kmmainwidget.h"
45 #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff...
46 #include "messageviewer/stl_util.h"
47 #include "messagecomposer/util.h"
48 #include <MessageCore/StringUtil>
49 #include "util.h"
50 #include "editor/widgets/snippetwidget.h"
51 #include "templatesconfiguration_kfg.h"
52 #include "mailcommon/foldercollectionmonitor.h"
53 #include "mailcommon/mailkernel.h"
54 #include "custommimeheader.h"
55 #include "PimCommon/LineEditWithAutoCorrection"
56 #include "PimCommon/CustomToolsWidgetng"
57 #include "warningwidgets/attachmentmissingwarning.h"
58 #include "job/createnewcontactjob.h"
59 #include "job/savedraftjob.h"
60 #include "warningwidgets/externaleditorwarning.h"
61 #include "widgets/cryptostateindicatorwidget.h"
62 #include "validatesendmailshortcut.h"
63 #include "job/saveasfilejob.h"
64 #include "messagecomposer/followupreminderselectdatedialog.h"
65 #include "messagecomposer/followupremindercreatejob.h"
66 #include "FollowupReminder/FollowUpReminderUtil"
67 #include "editor/potentialphishingemail/potentialphishingemailwarning.h"
68 #include "kmcomposerglobalaction.h"
69 #include "widgets/kactionmenutransport.h"
70 #include "pimcommon/kactionmenuchangecase.h"
72 #include <Libkdepim/StatusbarProgressWidget>
73 #include <Libkdepim/ProgressStatusBarWidget>
75 #include "KPIMTextEdit/EditorUtil"
76 #include "plugineditorinterface.h"
77 #include "editor/plugininterface/kmailplugineditormanagerinterface.h"
78 #include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h"
79 #include <MessageComposer/PluginEditorCheckBeforeSendParams>
80 #include <MessageComposer/Util>
82 #include <kcontacts/vcardconverter.h>
83 #include "SendLater/SendLaterUtil"
84 #include "SendLater/SendLaterDialog"
85 #include "SendLater/SendLaterInfo"
87 // KDEPIM includes
88 #include <Libkleo/ProgressDialog>
89 #include <Libkleo/KeySelectionDialog>
90 #include <Libkleo/CryptoBackendFactory>
91 #include <Libkleo/ExportJob>
92 #include <Libkleo/SpecialJob>
94 #ifndef QT_NO_CURSOR
95 #include <Libkdepim/KCursorSaver>
96 #endif
98 #include <MessageViewer/MessageViewerSettings>
99 #include <MimeTreeParser/ObjectTreeParser>
100 #include <MimeTreeParser/NodeHelper>
101 #include <MessageComposer/Composer>
102 #include <MessageComposer/GlobalPart>
103 #include <MessageComposer/InfoPart>
104 #include <MessageComposer/TextPart>
105 #include <messagecomposer/messagecomposersettings.h>
106 #include <MessageComposer/MessageHelper>
107 #include <MessageComposer/SignatureController>
108 #include <MessageComposer/InsertTextFileJob>
109 #include <MessageComposer/ComposerLineEdit>
111 #include <MessageCore/AttachmentPart>
112 #include <MessageCore/MessageCoreSettings>
113 #include <templateparser.h>
114 #include <TemplateParser/TemplatesConfiguration>
115 #include <MessageCore/NodeHelper>
116 #include <Akonadi/KMime/MessageStatus>
117 #include "messagecore/messagehelpers.h"
118 #include <MailCommon/FolderRequester>
119 #include <MailCommon/FolderCollection>
121 #include "widgets/statusbarlabeltoggledstate.h"
123 // KDEPIMLIBS includes
124 #include <AkonadiCore/changerecorder.h>
125 #include <AkonadiCore/Monitor>
126 #include <AkonadiCore/ItemFetchJob>
127 #include <KIdentityManagement/kidentitymanagement/identitymanager.h>
128 #include <KIdentityManagement/kidentitymanagement/identitycombo.h>
129 #include <KIdentityManagement/kidentitymanagement/identity.h>
130 #include <KIdentityManagement/kidentitymanagement/signature.h>
131 #include <MailTransport/mailtransport/transportcombobox.h>
132 #include <MailTransport/mailtransport/transportmanager.h>
133 #include <MailTransport/mailtransport/transport.h>
134 #include <Akonadi/KMime/MessageFlags>
135 #include <kmime/kmime_message.h>
136 #include <kpimtextedit/selectspecialchardialog.h>
138 // KDELIBS includes
139 #include <kactioncollection.h>
140 #include <kactionmenu.h>
141 #include <kcharsets.h>
142 #include "kmail_debug.h"
143 #include <kdescendantsproxymodel.h>
144 #include <kedittoolbar.h>
146 #include <QShortcut>
147 #include <kmessagebox.h>
148 #include <krecentfilesaction.h>
149 #include <kshortcutsdialog.h>
151 #include <kstandardshortcut.h>
152 #include <ktoggleaction.h>
153 #include <ktoolbar.h>
154 #include <ktoolinvocation.h>
155 #include <sonnet/dictionarycombobox.h>
156 #include <krun.h>
157 #include <KIO/JobUiDelegate>
158 #include <QFileDialog>
159 #include <KEmailAddress>
160 #include <KEncodingFileDialog>
161 #include <KHelpClient>
162 #include <KCharsets>
163 #include <KConfigGroup>
164 #include <KXMLGUIFactory>
166 // Qt includes
167 #include <QMenu>
168 #include <qinputdialog.h>
169 #include <qstatusbar.h>
170 #include <QTemporaryDir>
171 #include <QAction>
172 #include <QClipboard>
173 #include <QSplitter>
174 #include <QMimeData>
175 #include <QTextDocumentWriter>
176 #include <QApplication>
177 #include <QCheckBox>
178 #include <QStandardPaths>
179 #include <QFontDatabase>
180 #include <QMimeDatabase>
181 #include <QMimeType>
183 // System includes
184 #include <stdlib.h>
185 #include <unistd.h>
186 #include <fcntl.h>
187 #include <memory>
188 #include <KSplitterCollapserButton>
189 #include <Akonadi/Contact/ContactGroupExpandJob>
190 #include <editor/potentialphishingemail/potentialphishingemailjob.h>
192 using Sonnet::DictionaryComboBox;
193 using MailTransport::TransportManager;
194 using MailTransport::Transport;
196 KMail::Composer *KMail::makeComposer(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context,
197 uint identity, const QString &textSelection,
198 const QString &customTemplate)
200 return KMComposerWin::create(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate);
203 KMail::Composer *KMComposerWin::create(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context,
204 uint identity, const QString &textSelection,
205 const QString &customTemplate)
207 return new KMComposerWin(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate);
210 int KMComposerWin::s_composerNumber = 0;
212 KMComposerWin::KMComposerWin(const KMime::Message::Ptr &aMsg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint id,
213 const QString &textSelection, const QString &customTemplate)
214 : KMail::Composer("kmail-composer#"),
215 mDone(false),
216 mTextSelection(textSelection),
217 mCustomTemplate(customTemplate),
218 mSigningAndEncryptionExplicitlyDisabled(false),
219 mFolder(Akonadi::Collection(-1)),
220 mForceDisableHtml(false),
221 mId(id),
222 mContext(context),
223 mSignAction(Q_NULLPTR), mEncryptAction(Q_NULLPTR), mRequestMDNAction(Q_NULLPTR),
224 mUrgentAction(Q_NULLPTR), mAllFieldsAction(Q_NULLPTR), mFromAction(Q_NULLPTR),
225 mReplyToAction(Q_NULLPTR), mSubjectAction(Q_NULLPTR),
226 mIdentityAction(Q_NULLPTR), mTransportAction(Q_NULLPTR), mFccAction(Q_NULLPTR),
227 mWordWrapAction(Q_NULLPTR), mFixedFontAction(Q_NULLPTR), mAutoSpellCheckingAction(Q_NULLPTR),
228 mDictionaryAction(Q_NULLPTR), mSnippetAction(Q_NULLPTR),
229 mAppendSignature(Q_NULLPTR), mPrependSignature(Q_NULLPTR), mInsertSignatureAtCursorPosition(Q_NULLPTR),
230 mCodecAction(Q_NULLPTR),
231 mCryptoModuleAction(Q_NULLPTR),
232 mFindText(Q_NULLPTR),
233 mFindNextText(Q_NULLPTR),
234 mReplaceText(Q_NULLPTR),
235 mSelectAll(Q_NULLPTR),
236 mDummyComposer(Q_NULLPTR),
237 mLabelWidth(0),
238 mComposerBase(Q_NULLPTR),
239 m_verifyMissingAttachment(Q_NULLPTR),
240 mPreventFccOverwrite(false),
241 mCheckForForgottenAttachments(true),
242 mWasModified(false),
243 mCryptoStateIndicatorWidget(Q_NULLPTR),
244 mSendNowByShortcutUsed(false),
245 mFollowUpToggleAction(Q_NULLPTR),
246 mStatusBarLabelToggledOverrideMode(Q_NULLPTR),
247 mStatusBarLabelSpellCheckingChangeMode(Q_NULLPTR),
248 mPluginEditorManagerInterface(Q_NULLPTR)
250 mGlobalAction = new KMComposerGlobalAction(this, this);
251 mComposerBase = new MessageComposer::ComposerViewBase(this, this);
252 mComposerBase->setIdentityManager(kmkernel->identityManager());
254 mPluginEditorManagerInterface = new KMailPluginEditorManagerInterface(this);
255 mPluginEditorCheckBeforeSendManagerInterface = new KMailPluginEditorCheckBeforeSendManagerInterface(this);
256 mPluginEditorCheckBeforeSendManagerInterface->setIdentityManagement(kmkernel->identityManager());
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);
264 (void) new MailcomposerAdaptor(this);
265 mdbusObjectPath = QLatin1String("/Composer_") + QString::number(++s_composerNumber);
266 QDBusConnection::sessionBus().registerObject(mdbusObjectPath, this);
268 MessageComposer::SignatureController *sigController = new MessageComposer::SignatureController(this);
269 connect(sigController, &MessageComposer::SignatureController::enableHtml, this, &KMComposerWin::enableHtml);
270 mComposerBase->setSignatureController(sigController);
272 if (!kmkernel->xmlGuiInstanceName().isEmpty()) {
273 setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2"));
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(),
282 QSizePolicy::Expanding);
283 mHeadersToEditorSplitter->addWidget(mHeadersArea);
284 QList<int> defaultSizes;
285 defaultSizes << 0;
286 mHeadersToEditorSplitter->setSizes(defaultSizes);
288 QVBoxLayout *v = new QVBoxLayout(mMainWidget);
289 v->setMargin(0);
290 v->addWidget(mHeadersToEditorSplitter);
291 KIdentityManagement::IdentityCombo *identity = new KIdentityManagement::IdentityCombo(kmkernel->identityManager(),
292 mHeadersArea);
293 identity->setToolTip(i18n("Select an identity for this message"));
294 identity->setCurrentIdentity(mId);
295 mComposerBase->setIdentityCombo(identity);
297 sigController->setIdentityCombo(identity);
298 sigController->suspend(); // we have to do identity change tracking ourselves due to the template code
300 Sonnet::DictionaryComboBox *dictionaryCombo = new DictionaryComboBox(mHeadersArea);
301 dictionaryCombo->setToolTip(i18n("Select the dictionary to use when spell-checking this message"));
302 mComposerBase->setDictionary(dictionaryCombo);
304 mFccFolder = new MailCommon::FolderRequester(mHeadersArea);
305 mFccFolder->setNotAllowToCreateNewFolder(true);
306 mFccFolder->setMustBeReadWrite(true);
308 mFccFolder->setToolTip(i18n("Select the sent-mail folder where a copy of this message will be saved"));
309 connect(mFccFolder, &MailCommon::FolderRequester::folderChanged, this, &KMComposerWin::slotFccFolderChanged);
311 MailTransport::TransportComboBox *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, static_cast<void (MailTransport::TransportComboBox::*)(int)>(&MailTransport::TransportComboBox::activated), this, &KMComposerWin::slotTransportChanged);
316 mEdtFrom = new MessageComposer::ComposerLineEdit(false, mHeadersArea);
317 mEdtFrom->setObjectName(QStringLiteral("fromLine"));
318 mEdtFrom->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
319 mEdtFrom->setToolTip(i18n("Set the \"From:\" email address for this message"));
320 mEdtReplyTo = new MessageComposer::ComposerLineEdit(true, mHeadersArea);
321 mEdtReplyTo->setObjectName(QStringLiteral("replyToLine"));
322 mEdtReplyTo->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
323 mEdtReplyTo->setToolTip(i18n("Set the \"Reply-To:\" email address for this message"));
324 connect(mEdtReplyTo, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
326 MessageComposer::RecipientsEditor *recipientsEditor = new MessageComposer::RecipientsEditor(mHeadersArea);
327 recipientsEditor->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config());
328 connect(recipientsEditor, &MessageComposer::RecipientsEditor::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
329 connect(recipientsEditor, &MessageComposer::RecipientsEditor::sizeHintChanged, this, &KMComposerWin::recipientEditorSizeHintChanged);
330 mComposerBase->setRecipientsEditor(recipientsEditor);
332 mEdtSubject = new PimCommon::LineEditWithAutoCorrection(mHeadersArea, QStringLiteral("kmail2rc"));
333 mEdtSubject->setActivateLanguageMenu(false);
334 mEdtSubject->setToolTip(i18n("Set a subject for this message"));
335 mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection());
336 mLblIdentity = new QLabel(i18n("&Identity:"), mHeadersArea);
337 mDictionaryLabel = new QLabel(i18n("&Dictionary:"), mHeadersArea);
338 mLblFcc = new QLabel(i18n("&Sent-Mail folder:"), mHeadersArea);
339 mLblTransport = new QLabel(i18n("&Mail transport:"), mHeadersArea);
340 mLblFrom = new QLabel(i18nc("sender address field", "&From:"), mHeadersArea);
341 mLblReplyTo = new QLabel(i18n("&Reply to:"), mHeadersArea);
342 mLblSubject = new QLabel(i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea);
343 mShowHeaders = KMailSettings::self()->headers();
344 mDone = false;
345 mGrid = Q_NULLPTR;
346 mFixedFontAction = Q_NULLPTR;
347 // the attachment view is separated from the editor by a splitter
348 mSplitter = new QSplitter(Qt::Vertical, mMainWidget);
349 mSplitter->setObjectName(QStringLiteral("mSplitter"));
350 mSplitter->setChildrenCollapsible(false);
351 mSnippetSplitter = new QSplitter(Qt::Horizontal, mSplitter);
352 mSnippetSplitter->setObjectName(QStringLiteral("mSnippetSplitter"));
353 mSplitter->addWidget(mSnippetSplitter);
355 QWidget *editorAndCryptoStateIndicators = new QWidget(mSplitter);
356 mCryptoStateIndicatorWidget = new CryptoStateIndicatorWidget;
357 mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator());
359 QVBoxLayout *vbox = new QVBoxLayout(editorAndCryptoStateIndicators);
360 vbox->setMargin(0);
362 KMComposerEditorNg *editor = new KMComposerEditorNg(this, mCryptoStateIndicatorWidget);
363 mRichTextEditorwidget = new KPIMTextEdit::RichTextEditorWidget(editor, mCryptoStateIndicatorWidget);
365 //Don't use new connect api here. It crashs
366 connect(editor, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged()));
367 //connect(editor, &KMComposerEditor::textChanged, this, &KMComposeWin::slotEditorTextChanged);
368 mComposerBase->setEditor(editor);
369 vbox->addWidget(mCryptoStateIndicatorWidget);
370 vbox->addWidget(mRichTextEditorwidget);
372 mSnippetSplitter->insertWidget(0, editorAndCryptoStateIndicators);
373 mSnippetSplitter->setOpaqueResize(true);
374 sigController->setEditor(editor);
376 mHeadersToEditorSplitter->addWidget(mSplitter);
377 editor->setAcceptDrops(true);
378 connect(sigController, &MessageComposer::SignatureController::signatureAdded,
379 mComposerBase->editor()->externalComposer(), &KPIMTextEdit::RichTextExternalComposer::startExternalEditor);
381 connect(dictionaryCombo, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KMComposerWin::slotSpellCheckingLanguage);
383 connect(editor, &KMComposerEditorNg::languageChanged, this, &KMComposerWin::slotDictionaryLanguageChanged);
384 connect(editor, &KMComposerEditorNg::spellCheckStatus, this, &KMComposerWin::slotSpellCheckingStatus);
385 connect(editor, &KMComposerEditorNg::insertModeChanged, this, &KMComposerWin::slotOverwriteModeChanged);
386 connect(editor, &KMComposerEditorNg::spellCheckingFinished, this, &KMComposerWin::slotCheckSendNow);
387 mSnippetWidget = new SnippetWidget(editor, actionCollection(), mSnippetSplitter);
388 mSnippetWidget->setVisible(KMailSettings::self()->showSnippetManager());
389 mSnippetSplitter->addWidget(mSnippetWidget);
390 mSnippetSplitter->setCollapsible(0, false);
391 mSnippetSplitterCollapser = new KSplitterCollapserButton(mSnippetWidget, mSnippetSplitter);
392 mSnippetSplitterCollapser->setVisible(KMailSettings::self()->showSnippetManager());
394 mSplitter->setOpaqueResize(true);
396 setWindowTitle(i18n("Composer"));
397 setMinimumSize(200, 200);
399 mCustomToolsWidget = new PimCommon::CustomToolsWidgetNg(actionCollection(), this);
400 mSplitter->addWidget(mCustomToolsWidget);
401 connect(mCustomToolsWidget, &PimCommon::CustomToolsWidgetNg::insertText, this, &KMComposerWin::slotInsertShortUrl);
403 MessageComposer::AttachmentModel *attachmentModel = new MessageComposer::AttachmentModel(this);
404 KMail::AttachmentView *attachmentView = new KMail::AttachmentView(attachmentModel, mSplitter);
405 attachmentView->hideIfEmpty();
406 connect(attachmentView, &KMail::AttachmentView::modified, this, &KMComposerWin::setModified);
407 KMail::AttachmentController *attachmentController = new KMail::AttachmentController(attachmentModel, attachmentView, this);
409 mComposerBase->setAttachmentModel(attachmentModel);
410 mComposerBase->setAttachmentController(attachmentController);
412 mAttachmentMissing = new AttachmentMissingWarning(this);
413 connect(mAttachmentMissing, &AttachmentMissingWarning::attachMissingFile, this, &KMComposerWin::slotAttachMissingFile);
414 connect(mAttachmentMissing, &AttachmentMissingWarning::explicitClosedMissingAttachment, this, &KMComposerWin::slotExplicitClosedMissingAttachment);
415 v->addWidget(mAttachmentMissing);
417 mPotentialPhishingEmailWarning = new PotentialPhishingEmailWarning(this);
418 connect(mPotentialPhishingEmailWarning, &PotentialPhishingEmailWarning::sendNow, this, &KMComposerWin::slotCheckSendNowStep2);
419 v->addWidget(mPotentialPhishingEmailWarning);
421 if (KMailSettings::self()->showForgottenAttachmentWarning()) {
422 m_verifyMissingAttachment = new QTimer(this);
423 m_verifyMissingAttachment->setSingleShot(true);
424 m_verifyMissingAttachment->setInterval(1000 * 5);
425 connect(m_verifyMissingAttachment, &QTimer::timeout, this, &KMComposerWin::slotVerifyMissingAttachmentTimeout);
427 connect(attachmentController, &KMail::AttachmentController::fileAttached, mAttachmentMissing, &AttachmentMissingWarning::slotFileAttached);
429 mExternalEditorWarning = new ExternalEditorWarning(this);
430 v->addWidget(mExternalEditorWarning);
432 mPluginEditorManagerInterface->setParentWidget(this);
433 mPluginEditorManagerInterface->setRichTextEditor(mRichTextEditorwidget->editor());
434 mPluginEditorManagerInterface->setActionCollection(actionCollection());
436 mPluginEditorCheckBeforeSendManagerInterface->setParentWidget(this);
438 setupStatusBar(attachmentView->widget());
439 setupActions();
440 setupEditor();
441 rethinkFields();
442 readConfig();
444 updateSignatureAndEncryptionStateIndicators();
446 applyMainWindowSettings(KMKernel::self()->config()->group("Composer"));
448 connect(mEdtSubject, &PimCommon::LineEditWithAutoCorrection::textChanged, this, &KMComposerWin::slotUpdateWindowTitle);
449 connect(identity, SIGNAL(identityChanged(uint)),
450 SLOT(slotIdentityChanged(uint)));
451 connect(kmkernel->identityManager(), SIGNAL(changed(uint)),
452 SLOT(slotIdentityChanged(uint)));
454 connect(mEdtFrom, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged);
455 connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::collectionRemoved, this, &KMComposerWin::slotFolderRemoved);
456 connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::slotConfigChanged);
458 mMainWidget->resize(800, 600);
459 setCentralWidget(mMainWidget);
461 if (KMailSettings::self()->useHtmlMarkup()) {
462 enableHtml();
463 } else {
464 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
467 if (KMailSettings::self()->useExternalEditor()) {
468 editor->setUseExternalEditor(true);
469 editor->setExternalEditorPath(KMailSettings::self()->externalEditor());
472 if (aMsg) {
473 setMessage(aMsg, lastSignState, lastEncryptState);
476 mComposerBase->recipientsEditor()->setFocus();
477 editor->composerActions()->updateActionStates(); // set toolbar buttons to correct values
479 mDone = true;
481 mDummyComposer = new MessageComposer::Composer(this);
482 mDummyComposer->globalPart()->setParentWidgetForGui(this);
485 KMComposerWin::~KMComposerWin()
487 writeConfig();
489 // When we have a collection set, store the message back to that collection.
490 // Note that when we save the message or sent it, mFolder is set back to 0.
491 // So this for example kicks in when opening a draft and then closing the window.
492 if (mFolder.isValid() && mMsg && isModified()) {
493 SaveDraftJob *saveDraftJob = new SaveDraftJob(mMsg, mFolder);
494 saveDraftJob->start();
497 delete mComposerBase;
500 void KMComposerWin::slotSpellCheckingLanguage(const QString &language)
502 mComposerBase->editor()->setSpellCheckingLanguage(language);
503 mEdtSubject->setSpellCheckingLanguage(language);
506 QString KMComposerWin::dbusObjectPath() const
508 return mdbusObjectPath;
511 void KMComposerWin::slotEditorTextChanged()
513 const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty();
514 mFindText->setEnabled(textIsNotEmpty);
515 mFindNextText->setEnabled(textIsNotEmpty);
516 mReplaceText->setEnabled(textIsNotEmpty);
517 mSelectAll->setEnabled(textIsNotEmpty);
518 if (m_verifyMissingAttachment && !m_verifyMissingAttachment->isActive()) {
519 m_verifyMissingAttachment->start();
523 void KMComposerWin::send(int how)
525 switch (how) {
526 case 1:
527 slotSendNow();
528 break;
529 default:
530 case 0:
531 // TODO: find out, what the default send method is and send it this way
532 case 2:
533 slotSendLater();
534 break;
538 void KMComposerWin::addAttachmentsAndSend(const QList<QUrl> &urls, const QString &comment, int how)
540 const int nbUrl = urls.count();
541 for (int i = 0; i < nbUrl; ++i) {
542 mComposerBase->addAttachment(urls.at(i), comment, true);
545 send(how);
548 void KMComposerWin::addAttachment(const QUrl &url, const QString &comment)
550 mComposerBase->addAttachment(url, comment, false);
553 void KMComposerWin::addAttachment(const QString &name,
554 KMime::Headers::contentEncoding cte,
555 const QString &charset,
556 const QByteArray &data,
557 const QByteArray &mimeType)
559 Q_UNUSED(cte);
560 mComposerBase->addAttachment(name, name, charset, data, mimeType);
563 void KMComposerWin::readConfig(bool reload /* = false */)
565 mEdtFrom->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode());
566 mComposerBase->recipientsEditor()->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode());
567 mEdtReplyTo->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode());
569 if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
570 mBodyFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
571 mFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
572 } else {
573 mBodyFont = KMailSettings::self()->composerFont();
574 mFixedFont = MessageViewer::MessageViewerSettings::self()->fixedFont();
577 slotUpdateFont();
578 mEdtFrom->setFont(mBodyFont);
579 mEdtReplyTo->setFont(mBodyFont);
580 mEdtSubject->setFont(mBodyFont);
582 if (!reload) {
583 QSize siz = KMailSettings::self()->composerSize();
584 if (siz.width() < 200) {
585 siz.setWidth(200);
587 if (siz.height() < 200) {
588 siz.setHeight(200);
590 resize(siz);
592 if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) {
593 mSnippetSplitter->setSizes(KMailSettings::self()->snippetSplitterPosition());
594 } else {
595 QList<int> defaults;
596 defaults << (int)(width() * 0.8) << (int)(width() * 0.2);
597 mSnippetSplitter->setSizes(defaults);
601 mComposerBase->identityCombo()->setCurrentIdentity(mId);
602 qCDebug(KMAIL_LOG) << mComposerBase->identityCombo()->currentIdentityName();
603 const KIdentityManagement::Identity &ident =
604 kmkernel->identityManager()->identityForUoid(mId);
606 mComposerBase->setAutoSaveInterval(KMailSettings::self()->autosaveInterval() * 1000 * 60);
608 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
610 QString fccName;
611 if (!ident.fcc().isEmpty()) {
612 fccName = ident.fcc();
614 setFcc(fccName);
617 void KMComposerWin::writeConfig(void)
619 KMailSettings::self()->setHeaders(mShowHeaders);
620 KMailSettings::self()->setCurrentTransport(mComposerBase->transportComboBox()->currentText());
621 KMailSettings::self()->setPreviousIdentity(mComposerBase->identityCombo()->currentIdentity());
622 KMailSettings::self()->setPreviousFcc(QString::number(mFccFolder->collection().id()));
623 KMailSettings::self()->setPreviousDictionary(mComposerBase->dictionary()->currentDictionaryName());
624 KMailSettings::self()->setAutoSpellChecking(mAutoSpellCheckingAction->isChecked());
625 MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mFixedFontAction->isChecked());
626 if (!mForceDisableHtml) {
627 KMailSettings::self()->setUseHtmlMarkup(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
629 KMailSettings::self()->setComposerSize(size());
630 KMailSettings::self()->setShowSnippetManager(mSnippetAction->isChecked());
632 KConfigGroup grp(KMKernel::self()->config()->group("Composer"));
633 saveMainWindowSettings(grp);
634 if (mSnippetAction->isChecked()) {
635 KMailSettings::setSnippetSplitterPosition(mSnippetSplitter->sizes());
638 // make sure config changes are written to disk, cf. bug 127538
639 KMKernel::self()->slotSyncConfig();
642 MessageComposer::Composer *KMComposerWin::createSimpleComposer()
644 QList< QByteArray > charsets = mCodecAction->mimeCharsets();
645 if (!mOriginalPreferredCharset.isEmpty()) {
646 charsets.insert(0, mOriginalPreferredCharset);
648 mComposerBase->setFrom(from());
649 mComposerBase->setReplyTo(replyTo());
650 mComposerBase->setSubject(subject());
651 mComposerBase->setCharsets(charsets);
652 return mComposerBase->createSimpleComposer();
655 bool KMComposerWin::canSignEncryptAttachments() const
657 return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat;
660 void KMComposerWin::slotUpdateView(void)
662 if (!mDone) {
663 return; // otherwise called from rethinkFields during the construction
664 // which is not the intended behavior
667 //This sucks awfully, but no, I cannot get an activated(int id) from
668 // actionContainer()
669 KToggleAction *act = ::qobject_cast<KToggleAction *>(sender());
670 if (!act) {
671 return;
673 int id;
675 if (act == mAllFieldsAction) {
676 id = 0;
677 } else if (act == mIdentityAction) {
678 id = HDR_IDENTITY;
679 } else if (act == mTransportAction) {
680 id = HDR_TRANSPORT;
681 } else if (act == mFromAction) {
682 id = HDR_FROM;
683 } else if (act == mReplyToAction) {
684 id = HDR_REPLY_TO;
685 } else if (act == mSubjectAction) {
686 id = HDR_SUBJECT;
687 } else if (act == mFccAction) {
688 id = HDR_FCC;
689 } else if (act == mDictionaryAction) {
690 id = HDR_DICTIONARY;
691 } else {
692 qCDebug(KMAIL_LOG) << "Something is wrong (Oh, yeah?)";
693 return;
696 // sanders There's a bug here this logic doesn't work if no
697 // fields are shown and then show all fields is selected.
698 // Instead of all fields being shown none are.
699 if (!act->isChecked()) {
700 // hide header
701 if (id > 0) {
702 mShowHeaders = mShowHeaders & ~id;
703 } else {
704 mShowHeaders = std::abs(mShowHeaders);
706 } else {
707 // show header
708 if (id > 0) {
709 mShowHeaders |= id;
710 } else {
711 mShowHeaders = -std::abs(mShowHeaders);
714 rethinkFields(true);
717 int KMComposerWin::calcColumnWidth(int which, long allShowing, int width) const
719 if ((allShowing & which) == 0) {
720 return width;
723 QLabel *w;
724 if (which == HDR_IDENTITY) {
725 w = mLblIdentity;
726 } else if (which == HDR_DICTIONARY) {
727 w = mDictionaryLabel;
728 } else if (which == HDR_FCC) {
729 w = mLblFcc;
730 } else if (which == HDR_TRANSPORT) {
731 w = mLblTransport;
732 } else if (which == HDR_FROM) {
733 w = mLblFrom;
734 } else if (which == HDR_REPLY_TO) {
735 w = mLblReplyTo;
736 } else if (which == HDR_SUBJECT) {
737 w = mLblSubject;
738 } else {
739 return width;
742 w->setBuddy(mComposerBase->editor()); // set dummy so we don't calculate width of '&' for this label.
743 w->adjustSize();
744 w->show();
745 return qMax(width, w->sizeHint().width());
748 void KMComposerWin::rethinkFields(bool fromSlot)
750 //This sucks even more but again no ids. sorry (sven)
751 int mask, row;
752 long showHeaders;
754 if (mShowHeaders < 0) {
755 showHeaders = HDR_ALL;
756 } else {
757 showHeaders = mShowHeaders;
760 for (mask = 1, mNumHeaders = 0; mask <= showHeaders; mask <<= 1) {
761 if ((showHeaders & mask) != 0) {
762 ++mNumHeaders;
766 delete mGrid;
767 mGrid = new QGridLayout(mHeadersArea);
768 mGrid->setColumnStretch(0, 1);
769 mGrid->setColumnStretch(1, 100);
770 mGrid->setRowStretch(mNumHeaders + 1, 100);
772 row = 0;
774 mLabelWidth = mComposerBase->recipientsEditor()->setFirstColumnWidth(0) + 2;
775 if (std::abs(mShowHeaders)&HDR_IDENTITY) {
776 mLabelWidth = calcColumnWidth(HDR_IDENTITY, showHeaders, mLabelWidth);
778 if (std::abs(mShowHeaders)&HDR_DICTIONARY) {
779 mLabelWidth = calcColumnWidth(HDR_DICTIONARY, showHeaders, mLabelWidth);
781 if (std::abs(mShowHeaders)&HDR_FCC) {
782 mLabelWidth = calcColumnWidth(HDR_FCC, showHeaders, mLabelWidth);
784 if (std::abs(mShowHeaders)&HDR_TRANSPORT) {
785 mLabelWidth = calcColumnWidth(HDR_TRANSPORT, showHeaders, mLabelWidth);
787 if (std::abs(mShowHeaders)&HDR_FROM) {
788 mLabelWidth = calcColumnWidth(HDR_FROM, showHeaders, mLabelWidth);
790 if (std::abs(mShowHeaders)&HDR_REPLY_TO) {
791 mLabelWidth = calcColumnWidth(HDR_REPLY_TO, showHeaders, mLabelWidth);
793 if (std::abs(mShowHeaders)&HDR_SUBJECT) {
794 mLabelWidth = calcColumnWidth(HDR_SUBJECT, showHeaders, mLabelWidth);
797 if (!fromSlot) {
798 mAllFieldsAction->setChecked(showHeaders == HDR_ALL);
801 if (!fromSlot) {
802 mIdentityAction->setChecked(std::abs(mShowHeaders)&HDR_IDENTITY);
804 rethinkHeaderLine(showHeaders, HDR_IDENTITY, row, mLblIdentity, mComposerBase->identityCombo());
806 if (!fromSlot) {
807 mDictionaryAction->setChecked(std::abs(mShowHeaders)&HDR_DICTIONARY);
809 rethinkHeaderLine(showHeaders, HDR_DICTIONARY, row, mDictionaryLabel,
810 mComposerBase->dictionary());
812 if (!fromSlot) {
813 mFccAction->setChecked(std::abs(mShowHeaders)&HDR_FCC);
815 rethinkHeaderLine(showHeaders, HDR_FCC, row, mLblFcc, mFccFolder);
817 if (!fromSlot) {
818 mTransportAction->setChecked(std::abs(mShowHeaders)&HDR_TRANSPORT);
820 rethinkHeaderLine(showHeaders, HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox());
822 if (!fromSlot) {
823 mFromAction->setChecked(std::abs(mShowHeaders)&HDR_FROM);
825 rethinkHeaderLine(showHeaders, HDR_FROM, row, mLblFrom, mEdtFrom);
827 QWidget *prevFocus = mEdtFrom;
829 if (!fromSlot) {
830 mReplyToAction->setChecked(std::abs(mShowHeaders)&HDR_REPLY_TO);
832 rethinkHeaderLine(showHeaders, HDR_REPLY_TO, row, mLblReplyTo, mEdtReplyTo);
833 if (showHeaders & HDR_REPLY_TO) {
834 prevFocus = connectFocusMoving(prevFocus, mEdtReplyTo);
837 mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2);
838 ++row;
839 if (showHeaders & HDR_REPLY_TO) {
840 connect(mEdtReplyTo, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop);
841 connect(mComposerBase->recipientsEditor(), SIGNAL(focusUp()), mEdtReplyTo, SLOT(setFocus()));
842 } else {
843 connect(mEdtFrom, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop);
844 connect(mComposerBase->recipientsEditor(), SIGNAL(focusUp()), mEdtFrom, SLOT(setFocus()));
847 connect(mComposerBase->recipientsEditor(), SIGNAL(focusDown()), mEdtSubject, SLOT(setFocus()));
848 connect(mEdtSubject, &PimCommon::SpellCheckLineEdit::focusUp, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusBottom);
850 prevFocus = mComposerBase->recipientsEditor();
852 if (!fromSlot) {
853 mSubjectAction->setChecked(std::abs(mShowHeaders)&HDR_SUBJECT);
855 rethinkHeaderLine(showHeaders, HDR_SUBJECT, row, mLblSubject, mEdtSubject);
856 connectFocusMoving(mEdtSubject, mComposerBase->editor());
858 assert(row <= mNumHeaders + 1);
860 mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
862 mIdentityAction->setEnabled(!mAllFieldsAction->isChecked());
863 mDictionaryAction->setEnabled(!mAllFieldsAction->isChecked());
864 mTransportAction->setEnabled(!mAllFieldsAction->isChecked());
865 mFromAction->setEnabled(!mAllFieldsAction->isChecked());
866 if (mReplyToAction) {
867 mReplyToAction->setEnabled(!mAllFieldsAction->isChecked());
869 mFccAction->setEnabled(!mAllFieldsAction->isChecked());
870 mSubjectAction->setEnabled(!mAllFieldsAction->isChecked());
871 mComposerBase->recipientsEditor()->setFirstColumnWidth(mLabelWidth);
874 QWidget *KMComposerWin::connectFocusMoving(QWidget *prev, QWidget *next)
876 connect(prev, SIGNAL(focusDown()), next, SLOT(setFocus()));
877 connect(next, SIGNAL(focusUp()), prev, SLOT(setFocus()));
879 return next;
882 void KMComposerWin::rethinkHeaderLine(int aValue, int aMask, int &aRow,
883 QLabel *aLbl, QWidget *aCbx)
885 if (aValue & aMask) {
886 aLbl->setBuddy(aCbx);
887 aLbl->setFixedWidth(mLabelWidth);
888 mGrid->addWidget(aLbl, aRow, 0);
890 mGrid->addWidget(aCbx, aRow, 1);
891 aCbx->show();
892 aRow++;
893 } else {
894 aLbl->hide();
895 aCbx->hide();
899 void KMComposerWin::applyTemplate(uint uoid, uint uOldId)
901 const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid);
902 if (ident.isNull()) {
903 return;
905 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Templates");
906 header->fromUnicodeString(ident.templates(), "utf-8");
907 mMsg->setHeader(header);
909 TemplateParser::TemplateParser::Mode mode;
910 switch (mContext) {
911 case New:
912 mode = TemplateParser::TemplateParser::NewMessage;
913 break;
914 case Reply:
915 mode = TemplateParser::TemplateParser::Reply;
916 break;
917 case ReplyToAll:
918 mode = TemplateParser::TemplateParser::ReplyAll;
919 break;
920 case Forward:
921 mode = TemplateParser::TemplateParser::Forward;
922 break;
923 case NoTemplate:
924 return;
927 if (mode == TemplateParser::TemplateParser::NewMessage) {
928 TemplateParser::TemplateParser parser(mMsg, mode);
929 parser.setSelection(mTextSelection);
930 parser.setAllowDecryption(true);
931 parser.setIdentityManager(KMKernel::self()->identityManager());
932 if (!mCustomTemplate.isEmpty()) {
933 parser.process(mCustomTemplate, mMsg, mCollectionForNewMessage);
934 } else {
935 parser.processWithIdentity(uoid, mMsg, mCollectionForNewMessage);
937 mComposerBase->updateTemplate(mMsg);
938 updateSignature(uoid, uOldId);
939 return;
942 if (mMsg->headerByType("X-KMail-Link-Message")) {
943 Akonadi::Item::List items;
944 const QStringList serNums = mMsg->headerByType("X-KMail-Link-Message")->asUnicodeString().split(QLatin1Char(','));
945 items.reserve(serNums.count());
946 foreach (const QString &serNumStr, serNums) {
947 items << Akonadi::Item(serNumStr.toLongLong());
950 Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(items, this);
951 job->fetchScope().fetchFullPayload(true);
952 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
953 job->setProperty("mode", (int)mode);
954 job->setProperty("uoid", uoid);
955 job->setProperty("uOldid", uOldId);
956 connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDelayedApplyTemplate);
960 void KMComposerWin::slotDelayedApplyTemplate(KJob *job)
962 const Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
963 const Akonadi::Item::List items = fetchJob->items();
965 const TemplateParser::TemplateParser::Mode mode = static_cast<TemplateParser::TemplateParser::Mode>(fetchJob->property("mode").toInt());
966 const uint uoid = fetchJob->property("uoid").toUInt();
967 const uint uOldId = fetchJob->property("uOldid").toUInt();
969 TemplateParser::TemplateParser parser(mMsg, mode);
970 parser.setSelection(mTextSelection);
971 parser.setAllowDecryption(true);
972 parser.setWordWrap(MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
973 parser.setIdentityManager(KMKernel::self()->identityManager());
974 foreach (const Akonadi::Item &item, items) {
975 if (!mCustomTemplate.isEmpty()) {
976 parser.process(mCustomTemplate, MessageCore::Util::message(item));
977 } else {
978 parser.processWithIdentity(uoid, MessageCore::Util::message(item));
981 mComposerBase->updateTemplate(mMsg);
982 updateSignature(uoid, uOldId);
985 void KMComposerWin::updateSignature(uint uoid, uint uOldId)
987 const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid);
988 const KIdentityManagement::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid(uOldId);
989 mComposerBase->identityChanged(ident, oldIdentity, true);
992 void KMComposerWin::setCollectionForNewMessage(const Akonadi::Collection &folder)
994 mCollectionForNewMessage = folder;
997 void KMComposerWin::setQuotePrefix(uint uoid)
999 QString quotePrefix = mMsg->headerByType("X-KMail-QuotePrefix") ? mMsg->headerByType("X-KMail-QuotePrefix")->asUnicodeString() : QString();
1000 if (quotePrefix.isEmpty()) {
1001 // no quote prefix header, set quote prefix according in identity
1002 // TODO port templates to ComposerViewBase
1004 if (mCustomTemplate.isEmpty()) {
1005 const KIdentityManagement::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault(uoid);
1006 // Get quote prefix from template
1007 // ( custom templates don't specify custom quotes prefixes )
1008 TemplateParser::Templates quoteTemplate(
1009 TemplateParser::TemplatesConfiguration::configIdString(identity.uoid()));
1010 quotePrefix = quoteTemplate.quoteString();
1013 mComposerBase->editor()->setQuotePrefixName(MessageCore::StringUtil::formatQuotePrefix(quotePrefix,
1014 mMsg->from()->displayString()));
1017 void KMComposerWin::setupActions(void)
1019 KActionMenuTransport *actActionNowMenu, *actActionLaterMenu;
1021 if (MessageComposer::MessageComposerSettings::self()->sendImmediate()) {
1022 //default = send now, alternative = queue
1023 QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
1024 actionCollection()->addAction(QStringLiteral("send_default"), action);
1025 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Return));
1026 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNowByShortcut);
1028 // FIXME: change to mail_send_via icon when this exist.
1029 actActionNowMenu = new KActionMenuTransport(this);
1030 actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
1031 actActionNowMenu->setText(i18n("&Send Mail Via"));
1033 actActionNowMenu->setIconText(i18n("Send"));
1034 actionCollection()->addAction(QStringLiteral("send_default_via"), actActionNowMenu);
1036 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this);
1037 actionCollection()->addAction(QStringLiteral("send_alternative"), action);
1038 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1040 actActionLaterMenu = new KActionMenuTransport(this);
1041 actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue")));
1042 actActionLaterMenu->setText(i18n("Send &Later Via"));
1044 actActionLaterMenu->setIconText(i18nc("Queue the message for sending at a later date", "Queue"));
1045 actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionLaterMenu);
1047 } else {
1048 //default = queue, alternative = send now
1049 QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this);
1050 actionCollection()->addAction(QStringLiteral("send_default"), action);
1051 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater);
1052 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Return));
1054 actActionLaterMenu = new KActionMenuTransport(this);
1055 actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue")));
1056 actActionLaterMenu->setText(i18n("Send &Later Via"));
1057 actionCollection()->addAction(QStringLiteral("send_default_via"), actActionLaterMenu);
1059 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
1060 actionCollection()->addAction(QStringLiteral("send_alternative"), action);
1061 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNow);
1063 // FIXME: change to mail_send_via icon when this exits.
1064 actActionNowMenu = new KActionMenuTransport(this);
1065 actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
1066 actActionNowMenu->setText(i18n("&Send Mail Via"));
1067 actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionNowMenu);
1070 connect(actActionNowMenu, SIGNAL(triggered(bool)), this,
1071 SLOT(slotSendNow()));
1072 connect(actActionLaterMenu, &QAction::triggered, this,
1073 &KMComposerWin::slotSendLater);
1074 connect(actActionNowMenu, &KActionMenuTransport::transportSelected, this,
1075 &KMComposerWin::slotSendNowVia);
1076 connect(actActionLaterMenu, &KActionMenuTransport::transportSelected, this,
1077 &KMComposerWin::slotSendLaterVia);
1079 QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this);
1080 actionCollection()->addAction(QStringLiteral("save_in_drafts"), action);
1081 KMail::Util::addQActionHelpText(action, i18n("Save email in Draft folder"));
1082 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_S));
1083 connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveDraft);
1085 action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Template"), this);
1086 KMail::Util::addQActionHelpText(action, i18n("Save email in Template folder"));
1087 actionCollection()->addAction(QStringLiteral("save_in_templates"), action);
1088 connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveTemplate);
1090 action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this);
1091 KMail::Util::addQActionHelpText(action, i18n("Save email as text or html file"));
1092 actionCollection()->addAction(QStringLiteral("save_as_file"), action);
1093 connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveAsFile);
1095 action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this);
1096 actionCollection()->addAction(QStringLiteral("insert_file"), action);
1097 connect(action, &QAction::triggered, this, &KMComposerWin::slotInsertFile);
1099 mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")),
1100 i18n("&Insert Recent Text File"), this);
1101 actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction);
1102 connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &KMComposerWin::slotInsertRecentFile);
1103 connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &KMComposerWin::slotRecentListFileClear);
1104 mRecentAction->loadEntries(KMKernel::self()->config()->group(QString()));
1106 action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this);
1107 KMail::Util::addQActionHelpText(action, i18n("Open Address Book"));
1108 actionCollection()->addAction(QStringLiteral("addressbook"), action);
1109 if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) {
1110 action->setEnabled(false);
1111 } else {
1112 connect(action, &QAction::triggered, this, &KMComposerWin::slotAddressBook);
1114 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Composer"), this);
1115 actionCollection()->addAction(QStringLiteral("new_composer"), action);
1117 connect(action, &QAction::triggered, this, &KMComposerWin::slotNewComposer);
1118 actionCollection()->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::New));
1120 action = new QAction(i18n("Select &Recipients..."), this);
1121 actionCollection()->addAction(QStringLiteral("select_recipients"), action);
1122 connect(action, &QAction::triggered,
1123 mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::selectRecipients);
1124 action = new QAction(i18n("Save &Distribution List..."), this);
1125 actionCollection()->addAction(QStringLiteral("save_distribution_list"), action);
1126 connect(action, &QAction::triggered,
1127 mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::saveDistributionList);
1129 KStandardAction::print(this, &KMComposerWin::slotPrint, actionCollection());
1130 KStandardAction::printPreview(this, &KMComposerWin::slotPrintPreview, actionCollection());
1131 KStandardAction::close(this, &KMComposerWin::slotClose, actionCollection());
1133 KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection());
1134 KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection());
1135 KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection());
1136 KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection());
1137 KStandardAction::pasteText(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection());
1138 mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection());
1140 mFindText = KStandardAction::find(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFind, actionCollection());
1141 mFindNextText = KStandardAction::findNext(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFindNext, actionCollection());
1143 mReplaceText = KStandardAction::replace(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotReplace, actionCollection());
1144 actionCollection()->addAction(KStandardAction::Spelling, QStringLiteral("spellcheck"),
1145 mComposerBase->editor(), SLOT(slotCheckSpelling()));
1147 action = new QAction(i18n("Paste as Attac&hment"), this);
1148 actionCollection()->addAction(QStringLiteral("paste_att"), action);
1149 connect(action, &QAction::triggered, this, &KMComposerWin::slotPasteAsAttachment);
1151 action = new QAction(i18n("Cl&ean Spaces"), this);
1152 actionCollection()->addAction(QStringLiteral("clean_spaces"), action);
1153 connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace);
1155 mFixedFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this);
1156 actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction);
1157 connect(mFixedFontAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateFont);
1158 mFixedFontAction->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont());
1160 //these are checkable!!!
1161 mUrgentAction = new KToggleAction(
1162 i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this);
1163 actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction);
1164 mRequestMDNAction = new KToggleAction(i18n("&Request Disposition Notification"), this);
1165 actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction);
1166 mRequestMDNAction->setChecked(KMailSettings::self()->requestMDN());
1167 //----- Message-Encoding Submenu
1168 mCodecAction = new CodecAction(CodecAction::ComposerMode, this);
1169 actionCollection()->addAction(QStringLiteral("charsets"), mCodecAction);
1170 mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this);
1171 actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction);
1172 mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap());
1173 connect(mWordWrapAction, &KToggleAction::toggled, this, &KMComposerWin::slotWordWrapToggled);
1175 mSnippetAction = new KToggleAction(i18n("&Snippets"), this);
1176 actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction);
1177 connect(mSnippetAction, &KToggleAction::toggled, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged);
1178 mSnippetAction->setChecked(KMailSettings::self()->showSnippetManager());
1180 mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")),
1181 i18n("&Automatic Spellchecking"),
1182 this);
1183 actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction);
1184 const bool spellChecking = KMailSettings::self()->autoSpellChecking();
1185 const bool useKmailEditor = !KMailSettings::self()->useExternalEditor();
1186 const bool spellCheckingEnabled = useKmailEditor && spellChecking;
1187 mAutoSpellCheckingAction->setEnabled(useKmailEditor);
1189 mAutoSpellCheckingAction->setChecked(spellCheckingEnabled);
1190 slotAutoSpellCheckingToggled(spellCheckingEnabled);
1191 connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1192 connect(mComposerBase->editor(), &KPIMTextEdit::RichTextEditor::checkSpellingChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1194 connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::textModeChanged, this, &KMComposerWin::slotTextModeChanged);
1195 connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorClosed, this, &KMComposerWin::slotExternalEditorClosed);
1196 connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorStarted, this, &KMComposerWin::slotExternalEditorStarted);
1197 //these are checkable!!!
1198 markupAction = new KToggleAction(i18n("Rich Text Editing"), this);
1199 markupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
1200 markupAction->setIconText(i18n("Rich Text"));
1201 markupAction->setToolTip(i18n("Toggle rich text editing mode"));
1202 actionCollection()->addAction(QStringLiteral("html"), markupAction);
1203 connect(markupAction, &KToggleAction::triggered, this, &KMComposerWin::slotToggleMarkup);
1205 mAllFieldsAction = new KToggleAction(i18n("&All Fields"), this);
1206 actionCollection()->addAction(QStringLiteral("show_all_fields"), mAllFieldsAction);
1207 connect(mAllFieldsAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1208 mIdentityAction = new KToggleAction(i18n("&Identity"), this);
1209 actionCollection()->addAction(QStringLiteral("show_identity"), mIdentityAction);
1210 connect(mIdentityAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1211 mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this);
1212 actionCollection()->addAction(QStringLiteral("show_dictionary"), mDictionaryAction);
1213 connect(mDictionaryAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1214 mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this);
1215 actionCollection()->addAction(QStringLiteral("show_fcc"), mFccAction);
1216 connect(mFccAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1217 mTransportAction = new KToggleAction(i18n("&Mail Transport"), this);
1218 actionCollection()->addAction(QStringLiteral("show_transport"), mTransportAction);
1219 connect(mTransportAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1220 mFromAction = new KToggleAction(i18n("&From"), this);
1221 actionCollection()->addAction(QStringLiteral("show_from"), mFromAction);
1222 connect(mFromAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1223 mReplyToAction = new KToggleAction(i18n("&Reply To"), this);
1224 actionCollection()->addAction(QStringLiteral("show_reply_to"), mReplyToAction);
1225 connect(mReplyToAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1226 mSubjectAction = new KToggleAction(
1227 i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this);
1228 actionCollection()->addAction(QStringLiteral("show_subject"), mSubjectAction);
1229 connect(mSubjectAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView);
1230 //end of checkable
1232 mAppendSignature = new QAction(i18n("Append S&ignature"), this);
1233 actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature);
1234 connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1236 mPrependSignature = new QAction(i18n("Pr&epend Signature"), this);
1237 actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature);
1238 connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1240 mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this);
1241 actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition);
1242 connect(mInsertSignatureAtCursorPosition, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::insertSignatureAtCursor);
1244 mComposerBase->attachmentController()->createActions();
1246 setStandardToolBarMenuEnabled(true);
1248 KStandardAction::keyBindings(this, &KMComposerWin::slotEditKeys, actionCollection());
1249 KStandardAction::configureToolbars(this, &KMComposerWin::slotEditToolbars, actionCollection());
1250 KStandardAction::preferences(kmkernel, SLOT(slotShowConfigurationDialog()), actionCollection());
1252 action = new QAction(i18n("&Spellchecker..."), this);
1253 action->setIconText(i18n("Spellchecker"));
1254 actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action);
1255 connect(action, &QAction::triggered, this, &KMComposerWin::slotSpellcheckConfig);
1257 mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this);
1258 mEncryptAction->setIconText(i18n("Encrypt"));
1259 actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction);
1260 mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this);
1261 mSignAction->setIconText(i18n("Sign"));
1262 actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction);
1263 const KIdentityManagement::Identity &ident =
1264 KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity());
1265 // PENDING(marc): check the uses of this member and split it into
1266 // smime/openpgp and or enc/sign, if necessary:
1267 mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1268 mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1270 mLastEncryptActionState = false;
1271 mLastSignActionState = ident.pgpAutoSign();
1273 changeCryptoAction();
1275 connect(mEncryptAction, &KToggleAction::triggered, this, &KMComposerWin::slotEncryptToggled);
1276 connect(mSignAction, &KToggleAction::triggered, this, &KMComposerWin::slotSignToggled);
1278 QStringList listCryptoFormat;
1279 listCryptoFormat.reserve(numCryptoMessageFormats);
1280 for (int i = 0; i < numCryptoMessageFormats; ++i) {
1281 listCryptoFormat.push_back(Kleo::cryptoMessageFormatToLabel(cryptoMessageFormats[i]));
1284 mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this);
1285 actionCollection()->addAction(QStringLiteral("options_select_crypto"), mCryptoModuleAction);
1286 connect(mCryptoModuleAction, SIGNAL(triggered(int)), SLOT(slotSelectCryptoModule()));
1287 mCryptoModuleAction->setToolTip(i18n("Select a cryptographic format for this message"));
1288 mCryptoModuleAction->setItems(listCryptoFormat);
1290 mComposerBase->editor()->createActions(actionCollection());
1292 mFollowUpToggleAction = new KToggleAction(i18n("Follow Up Mail..."), this);
1293 actionCollection()->addAction(QStringLiteral("follow_up_mail"), mFollowUpToggleAction);
1294 connect(mFollowUpToggleAction, &KToggleAction::triggered, this, &KMComposerWin::slotFollowUpMail);
1295 mFollowUpToggleAction->setEnabled(FollowUpReminder::FollowUpReminderUtil::followupReminderAgentEnabled());
1297 mPluginEditorManagerInterface->initializePlugins();
1298 mPluginEditorCheckBeforeSendManagerInterface->initializePlugins();
1300 QShortcut *shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this);
1301 connect(shortcut, &QShortcut::activated, this, &KMComposerWin::slotInsertNonBreakingSpace);
1304 createGUI(QStringLiteral("kmcomposerui.rc"));
1305 initializePluginActions();
1306 connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled,
1307 this, &KMComposerWin::htmlToolBarVisibilityChanged);
1309 // In Kontact, this entry would read "Configure Kontact", but bring
1310 // up KMail's config dialog. That's sensible, though, so fix the label.
1311 QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure"));
1312 if (configureAction) {
1313 configureAction->setText(i18n("Configure KMail..."));
1317 void KMComposerWin::initializePluginActions()
1319 if (guiFactory()) {
1320 QHashIterator<MessageComposer::ActionType::Type, QList<QAction *> > localActionsType(mPluginEditorManagerInterface->actionsType());
1321 while (localActionsType.hasNext()) {
1322 localActionsType.next();
1323 QList<QAction *> lst = localActionsType.value();
1324 if (!lst.isEmpty()) {
1325 const QString actionlistname = QStringLiteral("kmaileditor") + MessageComposer::PluginEditorInterface::actionXmlExtension(localActionsType.key());
1326 Q_FOREACH (KXMLGUIClient *client, guiFactory()->clients()) {
1327 client->unplugActionList(actionlistname);
1328 client->plugActionList(actionlistname, lst);
1335 void KMComposerWin::changeCryptoAction()
1337 const KIdentityManagement::Identity &ident =
1338 KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity());
1340 if (!Kleo::CryptoBackendFactory::instance()->openpgp() && !Kleo::CryptoBackendFactory::instance()->smime()) {
1341 // no crypto whatsoever
1342 mEncryptAction->setEnabled(false);
1343 setEncryption(false);
1344 mSignAction->setEnabled(false);
1345 setSigning(false);
1346 } else {
1347 const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp() &&
1348 !ident.pgpSigningKey().isEmpty();
1349 const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime() &&
1350 !ident.smimeSigningKey().isEmpty();
1352 setEncryption(false);
1353 setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign());
1358 void KMComposerWin::setupStatusBar(QWidget *w)
1360 //KPIM::ProgressStatusBarWidget *progressStatusBarWidget = new KPIM::ProgressStatusBarWidget(statusBar(), this, PimCommon::StorageServiceProgressManager::progressTypeValue());
1361 statusBar()->addWidget(w);
1362 QLabel *lab = new QLabel(this);
1363 lab->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
1364 statusBar()->addPermanentWidget(lab);
1365 mStatusBarLabelList.append(lab);
1367 lab = new QLabel(this);
1368 lab->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 "
1369 , QStringLiteral(" ")));
1370 statusBar()->addPermanentWidget(lab);
1371 mStatusBarLabelList.append(lab);
1373 lab = new QLabel(i18n(" Column: %1 ", QStringLiteral(" ")));
1374 statusBar()->addPermanentWidget(lab);
1375 mStatusBarLabelList.append(lab);
1377 mStatusBarLabelToggledOverrideMode = new StatusBarLabelToggledState(this);
1378 mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS"));
1379 statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0);
1380 connect(mStatusBarLabelToggledOverrideMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotOverwriteModeWasChanged);
1382 mStatusBarLabelSpellCheckingChangeMode = new StatusBarLabelToggledState(this);
1383 mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off"));
1384 statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0);
1385 connect(mStatusBarLabelSpellCheckingChangeMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1387 //statusBar()->addPermanentWidget(progressStatusBarWidget->littleProgress());
1391 void KMComposerWin::setupEditor(void)
1393 QFontMetrics fm(mBodyFont);
1394 mComposerBase->editor()->setTabStopWidth(fm.width(QLatin1Char(' ')) * 8);
1396 slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap());
1398 // Font setup
1399 slotUpdateFont();
1401 connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged,
1402 this, &KMComposerWin::slotCursorPositionChanged);
1403 slotCursorPositionChanged();
1406 QString KMComposerWin::subject() const
1408 return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText());
1411 QString KMComposerWin::from() const
1413 return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text());
1416 QString KMComposerWin::replyTo() const
1418 if (mEdtReplyTo) {
1419 return MessageComposer::Util::cleanedUpHeaderString(mEdtReplyTo->text());
1420 } else {
1421 return QString();
1425 void KMComposerWin::setCurrentTransport(int transportId)
1427 mComposerBase->transportComboBox()->setCurrentTransport(transportId);
1430 void KMComposerWin::setCurrentReplyTo(const QString &replyTo)
1432 if (mEdtReplyTo) {
1433 mEdtReplyTo->setText(replyTo);
1437 uint KMComposerWin::currentIdentity() const
1439 return mComposerBase->identityCombo()->currentIdentity();
1442 void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg, bool lastSignState, bool lastEncryptState, bool mayAutoSign,
1443 bool allowDecryption, bool isModified)
1445 if (!newMsg) {
1446 qCDebug(KMAIL_LOG) << "newMsg == 0!";
1447 return;
1450 if (lastSignState) {
1451 mLastSignActionState = true;
1454 if (lastEncryptState) {
1455 mLastEncryptActionState = true;
1458 mComposerBase->setMessage(newMsg, allowDecryption);
1459 mMsg = newMsg;
1460 KIdentityManagement::IdentityManager *im = KMKernel::self()->identityManager();
1462 mEdtFrom->setText(mMsg->from()->asUnicodeString());
1463 mEdtSubject->setText(mMsg->subject()->asUnicodeString());
1465 // Restore the quote prefix. We can't just use the global quote prefix here,
1466 // since the prefix is different for each message, it might for example depend
1467 // on the original sender in a reply.
1468 if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) {
1469 mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString());
1472 if (newMsg->headerByType("X-KMail-Identity") &&
1473 !newMsg->headerByType("X-KMail-Identity")->asUnicodeString().isEmpty()) {
1474 mId = newMsg->headerByType("X-KMail-Identity")->asUnicodeString().toUInt();
1477 // don't overwrite the header values with identity specific values
1478 disconnect(mComposerBase->identityCombo(), SIGNAL(identityChanged(uint)),
1479 this, SLOT(slotIdentityChanged(uint)));
1481 // load the mId into the gui, sticky or not, without emitting
1482 mComposerBase->identityCombo()->setCurrentIdentity(mId);
1483 connect(mComposerBase->identityCombo(), SIGNAL(identityChanged(uint)),
1484 this, SLOT(slotIdentityChanged(uint)));
1486 // manually load the identity's value into the fields; either the one from the
1487 // messge, where appropriate, or the one from the sticky identity. What's in
1488 // mId might have changed meanwhile, thus the save value
1489 slotIdentityChanged(mId, true /*initalChange*/);
1490 // Fixing the identitis with auto signing activated
1491 mLastSignActionState = mSignAction->isChecked();
1493 const KIdentityManagement::Identity &ident = im->identityForUoid(mComposerBase->identityCombo()->currentIdentity());
1495 // check for the presence of a DNT header, indicating that MDN's were requested
1496 if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) {
1497 QString mdnAddr = hdr->asUnicodeString();
1498 mRequestMDNAction->setChecked((!mdnAddr.isEmpty() &&
1499 im->thatIsMe(mdnAddr)) ||
1500 KMailSettings::self()->requestMDN());
1502 // check for presence of a priority header, indicating urgent mail:
1503 if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) {
1504 const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString();
1505 const QString priority = newMsg->headerByType("Priority")->asUnicodeString();
1506 if (xpriority == QLatin1String("2 (High)") && priority == QLatin1String("urgent")) {
1507 mUrgentAction->setChecked(true);
1511 if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) {
1512 mMsg->removeHeader("X-Face");
1513 } else {
1514 QString xface = ident.xface();
1515 if (!xface.isEmpty()) {
1516 int numNL = (xface.length() - 1) / 70;
1517 for (int i = numNL; i > 0; --i) {
1518 xface.insert(i * 70, QStringLiteral("\n\t"));
1520 auto header = new KMime::Headers::Generic("X-Face");
1521 header->fromUnicodeString(xface, "utf-8");
1522 mMsg->setHeader(header);
1526 // if these headers are present, the state of the message should be overruled
1527 if (auto hdr = mMsg->headerByType("X-KMail-SignatureActionEnabled")) {
1528 mLastSignActionState = (hdr->as7BitString(false).contains("true"));
1530 if (auto hdr = mMsg->headerByType("X-KMail-EncryptActionEnabled")) {
1531 mLastEncryptActionState = (hdr->as7BitString(false).contains("true"));
1533 if (auto hdr = mMsg->headerByType("X-KMail-CryptoMessageFormat")) {
1534 mCryptoModuleAction->setCurrentItem(format2cb(static_cast<Kleo::CryptoMessageFormat>(
1535 hdr->asUnicodeString().toInt())));
1538 mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1539 mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1541 if (Kleo::CryptoBackendFactory::instance()->openpgp() || Kleo::CryptoBackendFactory::instance()->smime()) {
1542 const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp() &&
1543 !ident.pgpSigningKey().isEmpty();
1544 const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime() &&
1545 !ident.smimeSigningKey().isEmpty();
1547 setEncryption(mLastEncryptActionState);
1548 setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState);
1550 updateSignatureAndEncryptionStateIndicators();
1552 QString kmailFcc;
1553 if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) {
1554 kmailFcc = hdr->asUnicodeString();
1556 if (kmailFcc.isEmpty()) {
1557 setFcc(ident.fcc());
1558 } else {
1559 setFcc(kmailFcc);
1561 if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) {
1562 const QString dictionary = hdr->asUnicodeString();
1563 if (!dictionary.isEmpty()) {
1564 mComposerBase->dictionary()->setCurrentByDictionary(dictionary);
1566 } else {
1567 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
1570 mEdtReplyTo->setText(mMsg->replyTo()->asUnicodeString());
1572 KMime::Content *msgContent = new KMime::Content;
1573 msgContent->setContent(mMsg->encodedContent());
1574 msgContent->parse();
1575 MessageViewer::EmptySource emptySource;
1576 MimeTreeParser::ObjectTreeParser otp(&emptySource); //All default are ok
1577 emptySource.setAllowDecryption(allowDecryption);
1578 otp.parseObjectTree(msgContent);
1580 bool shouldSetCharset = false;
1581 if ((mContext == Reply || mContext == ReplyToAll || mContext == Forward) && MessageComposer::MessageComposerSettings::forceReplyCharset()) {
1582 shouldSetCharset = true;
1584 if (shouldSetCharset && !otp.plainTextContentCharset().isEmpty()) {
1585 mOriginalPreferredCharset = otp.plainTextContentCharset();
1587 // always set auto charset, but prefer original when composing if force reply is set.
1588 mCodecAction->setAutoCharset();
1590 delete msgContent;
1592 if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign) {
1594 // Espen 2000-05-16
1595 // Delay the signature appending. It may start a fileseletor.
1596 // Not user friendy if this modal fileseletor opens before the
1597 // composer.
1599 if (MessageComposer::MessageComposerSettings::self()->prependSignature()) {
1600 QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1601 } else {
1602 QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1604 } else {
1605 mComposerBase->editor()->externalComposer()->startExternalEditor();
1608 setModified(isModified);
1610 // honor "keep reply in this folder" setting even when the identity is changed later on
1611 mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc);
1612 QTimer::singleShot(0, this, &KMComposerWin::forceAutoSaveMessage); //Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in.
1615 void KMComposerWin::setAutoSaveFileName(const QString &fileName)
1617 mComposerBase->setAutoSaveFileName(fileName);
1620 void KMComposerWin::setSigningAndEncryptionDisabled(bool v)
1622 mSigningAndEncryptionExplicitlyDisabled = v;
1625 void KMComposerWin::setFolder(const Akonadi::Collection &aFolder)
1627 mFolder = aFolder;
1630 void KMComposerWin::setFcc(const QString &idString)
1632 // check if the sent-mail folder still exists
1633 Akonadi::Collection col;
1634 if (idString.isEmpty()) {
1635 col = CommonKernel->sentCollectionFolder();
1636 } else {
1637 col = Akonadi::Collection(idString.toLongLong());
1640 mComposerBase->setFcc(col);
1641 mFccFolder->setCollection(col);
1644 bool KMComposerWin::isComposerModified() const
1646 return (mComposerBase->editor()->document()->isModified() ||
1647 mEdtFrom->isModified() ||
1648 (mEdtReplyTo && mEdtReplyTo->isModified()) ||
1649 mComposerBase->recipientsEditor()->isModified() ||
1650 mEdtSubject->document()->isModified());
1653 bool KMComposerWin::isModified() const
1655 return mWasModified || isComposerModified();
1658 void KMComposerWin::setModified(bool modified)
1660 mWasModified = modified;
1661 changeModifiedState(modified);
1664 void KMComposerWin::changeModifiedState(bool modified)
1666 mComposerBase->editor()->document()->setModified(modified);
1667 if (!modified) {
1668 mEdtFrom->setModified(false);
1669 if (mEdtReplyTo) {
1670 mEdtReplyTo->setModified(false);
1672 mComposerBase->recipientsEditor()->clearModified();
1673 mEdtSubject->document()->setModified(false);
1677 bool KMComposerWin::queryClose()
1679 if (!mComposerBase->editor()->checkExternalEditorFinished()) {
1680 return false;
1682 if (kmkernel->shuttingDown() || qApp->isSavingSession()) {
1683 return true;
1686 if (isModified()) {
1687 const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder));
1688 const QString savebut = (istemplate ?
1689 i18n("Re&save as Template") :
1690 i18n("&Save as Draft"));
1691 const QString savetext = (istemplate ?
1692 i18n("Resave this message in the Templates folder. "
1693 "It can then be used at a later time.") :
1694 i18n("Save this message in the Drafts folder. "
1695 "It can then be edited and sent at a later time."));
1697 const int rc = KMessageBox::warningYesNoCancel(this,
1698 i18n("Do you want to save the message for later or discard it?"),
1699 i18n("Close Composer"),
1700 KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext),
1701 KStandardGuiItem::discard(),
1702 KStandardGuiItem::cancel());
1703 if (rc == KMessageBox::Cancel) {
1704 return false;
1705 } else if (rc == KMessageBox::Yes) {
1706 // doSend will close the window. Just return false from this method
1707 if (istemplate) {
1708 slotSaveTemplate();
1709 } else {
1710 slotSaveDraft();
1712 return false;
1714 //else fall through: return true
1716 mComposerBase->cleanupAutoSave();
1718 if (!mMiscComposers.isEmpty()) {
1719 qCWarning(KMAIL_LOG) << "Tried to close while composer was active";
1720 return false;
1722 return true;
1725 MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment()
1727 bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning();
1729 if (!checkForForgottenAttachments) {
1730 return MessageComposer::ComposerViewBase::NoMissingAttachmentFound;
1733 mComposerBase->setSubject(subject()); //be sure the composer knows the subject
1734 MessageComposer::ComposerViewBase::MissingAttachment missingAttachments = mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords());
1736 return missingAttachments;
1739 void KMComposerWin::forceAutoSaveMessage()
1741 autoSaveMessage(true);
1744 void KMComposerWin::autoSaveMessage(bool force)
1746 if (isComposerModified() || force) {
1747 applyComposerSetting(mComposerBase);
1748 mComposerBase->saveMailSettings();
1749 mComposerBase->autoSaveMessage();
1750 if (!force) {
1751 mWasModified = true;
1752 changeModifiedState(false);
1754 } else {
1755 mComposerBase->updateAutoSave();
1759 bool KMComposerWin::encryptToSelf() const
1761 return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf();
1764 void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type)
1766 setEnabled(true);
1767 if (!msg.isEmpty()) {
1768 KMessageBox::sorry(mMainWidget, msg,
1769 (type == MessageComposer::ComposerViewBase::AutoSave) ? i18n("Autosave Message Failed") : i18n("Sending Message Failed"));
1773 void KMComposerWin::slotSendSuccessful()
1775 setModified(false);
1776 mComposerBase->cleanupAutoSave();
1777 mFolder = Akonadi::Collection(); // see dtor
1778 close();
1781 const KIdentityManagement::Identity &KMComposerWin::identity() const
1783 return KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity());
1786 Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const
1788 if (!mCryptoModuleAction) {
1789 return Kleo::AutoFormat;
1791 return cb2format(mCryptoModuleAction->currentItem());
1794 void KMComposerWin::addAttach(KMime::Content *msgPart)
1796 mComposerBase->addAttachmentPart(msgPart);
1797 setModified(true);
1800 void KMComposerWin::slotAddressBook()
1802 KRun::runCommand(QStringLiteral("kaddressbook"), window());
1805 void KMComposerWin::slotInsertFile()
1807 const QUrl u = insertFile();
1808 if (u.isEmpty()) {
1809 return;
1812 mRecentAction->addUrl(u);
1813 // Prevent race condition updating list when multiple composers are open
1815 QUrlQuery query;
1816 const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset")));
1817 QStringList urls = KMailSettings::self()->recentUrls();
1818 QStringList encodings = KMailSettings::self()->recentEncodings();
1819 // Prevent config file from growing without bound
1820 // Would be nicer to get this constant from KRecentFilesAction
1821 const int mMaxRecentFiles = 30;
1822 while (urls.count() > mMaxRecentFiles) {
1823 urls.removeLast();
1825 while (encodings.count() > mMaxRecentFiles) {
1826 encodings.removeLast();
1828 // sanity check
1829 if (urls.count() != encodings.count()) {
1830 urls.clear();
1831 encodings.clear();
1833 urls.prepend(u.toDisplayString());
1834 encodings.prepend(encoding);
1835 KMailSettings::self()->setRecentUrls(urls);
1836 KMailSettings::self()->setRecentEncodings(encodings);
1837 mRecentAction->saveEntries(KMKernel::self()->config()->group(QString()));
1839 slotInsertRecentFile(u);
1842 void KMComposerWin::slotRecentListFileClear()
1844 KSharedConfig::Ptr config = KMKernel::self()->config();
1845 KConfigGroup group(config, "Composer");
1846 group.deleteEntry("recent-urls");
1847 group.deleteEntry("recent-encodings");
1848 mRecentAction->saveEntries(config->group(QString()));
1851 void KMComposerWin::slotInsertRecentFile(const QUrl &u)
1853 if (u.fileName().isEmpty()) {
1854 return;
1857 // Get the encoding previously used when inserting this file
1858 QString encoding;
1859 const QStringList urls = KMailSettings::self()->recentUrls();
1860 const QStringList encodings = KMailSettings::self()->recentEncodings();
1861 const int index = urls.indexOf(u.toDisplayString());
1862 if (index != -1) {
1863 encoding = encodings[ index ];
1864 } else {
1865 qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; //see InsertTextFileJob
1866 return;
1869 MessageComposer::InsertTextFileJob *job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u);
1870 job->setEncoding(encoding);
1871 connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile);
1872 job->start();
1875 bool KMComposerWin::showErrorMessage(KJob *job)
1877 if (job->error()) {
1878 if (static_cast<KIO::Job *>(job)->ui()) {
1879 static_cast<KIO::Job *>(job)->ui()->showErrorMessage();
1880 } else {
1881 qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString();
1883 return true;
1885 return false;
1888 void KMComposerWin::slotInsertTextFile(KJob *job)
1890 showErrorMessage(job);
1893 void KMComposerWin::slotSelectCryptoModule(bool init)
1895 if (!init) {
1896 setModified(true);
1899 mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments());
1900 mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments());
1903 void KMComposerWin::slotUpdateFont()
1905 if (!mFixedFontAction) {
1906 return;
1908 mComposerBase->editor()->composerControler()->setFontForWholeText(mFixedFontAction->isChecked() ?
1909 mFixedFont : mBodyFont);
1912 QUrl KMComposerWin::insertFile()
1914 const KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlAndEncoding(QString(),
1915 QUrl(),
1916 QString(),
1917 this,
1918 i18nc("@title:window", "Insert File"));
1919 QUrl url;
1920 if (!result.URLs.isEmpty()) {
1921 url = result.URLs.first();
1922 if (url.isValid()) {
1923 MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding));
1926 return url;
1929 QString KMComposerWin::smartQuote(const QString &msg)
1931 return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
1934 void KMComposerWin::insertUrls(const QMimeData *source, const QList<QUrl> &urlList)
1936 QStringList urlAdded;
1937 foreach (const QUrl &url, urlList) {
1938 QString urlStr;
1939 if (url.scheme() == QLatin1String("mailto")) {
1940 urlStr = KEmailAddress::decodeMailtoUrl(url);
1941 } else {
1942 urlStr = url.toDisplayString();
1943 // Workaround #346370
1944 if (urlStr.isEmpty()) {
1945 urlStr = source->text();
1948 if (!urlAdded.contains(urlStr)) {
1949 mComposerBase->editor()->composerControler()->insertLink(urlStr);
1950 urlAdded.append(urlStr);
1956 bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment)
1958 // If this is a PNG image, either add it as an attachment or as an inline image
1959 if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) {
1960 // Get the image data before showing the dialog, since that processes events which can delete
1961 // the QMimeData object behind our back
1962 const QByteArray imageData = source->data(QStringLiteral("image/png"));
1963 if (imageData.isEmpty()) {
1964 return true;
1966 if (!forceAttachment) {
1967 if (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
1968 QImage image = qvariant_cast<QImage>(source->imageData());
1969 QFileInfo fi(source->text());
1971 QMenu menu;
1972 const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image"));
1973 /*const QAction *addAsAttachmentAction = */menu.addAction(i18n("Add as &Attachment"));
1974 const QAction *selectedAction = menu.exec(QCursor::pos());
1975 if (selectedAction == addAsInlineImageAction) {
1976 // Let the textedit from kdepimlibs handle inline images
1977 mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi);
1978 return true;
1979 } else if (!selectedAction) {
1980 return true;
1982 // else fall through
1985 // Ok, when we reached this point, the user wants to add the image as an attachment.
1986 // Ask for the filename first.
1987 bool ok;
1988 const QString attName =
1989 QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
1990 if (!ok) {
1991 return true;
1993 addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png");
1994 return true;
1997 // If this is a URL list, add those files as attachments or text
1998 // but do not offer this if we are pasting plain text containing an url, e.g. from a browser
1999 const QList<QUrl> urlList = source->urls();
2000 if (!urlList.isEmpty()) {
2001 if (source->hasFormat(QStringLiteral("text/plain"))) {
2002 insertUrls(source, urlList);
2003 return true;
2004 } else {
2005 //Search if it's message items.
2006 Akonadi::Item::List items;
2007 Akonadi::Collection::List collections;
2008 bool allLocalURLs = true;
2010 foreach (const QUrl &url, urlList) {
2011 if (!url.isLocalFile()) {
2012 allLocalURLs = false;
2014 const Akonadi::Item item = Akonadi::Item::fromUrl(url);
2015 if (item.isValid()) {
2016 items << item;
2017 } else {
2018 const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url);
2019 if (collection.isValid()) {
2020 collections << collection;
2025 if (items.isEmpty() && collections.isEmpty()) {
2026 if (allLocalURLs || forceAttachment) {
2027 foreach (const QUrl &url, urlList) {
2028 addAttachment(url, QString());
2030 } else {
2031 QMenu p;
2032 const int sizeUrl(urlList.size());
2033 const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl));
2034 const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl));
2035 const QAction *selectedAction = p.exec(QCursor::pos());
2037 if (selectedAction == addAsTextAction) {
2038 insertUrls(source, urlList);
2039 } else if (selectedAction == addAsAttachmentAction) {
2040 foreach (const QUrl &url, urlList) {
2041 if (url.isValid()) {
2042 addAttachment(url, QString());
2047 return true;
2048 } else {
2049 if (!items.isEmpty()) {
2050 Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(items, this);
2051 itemFetchJob->fetchScope().fetchFullPayload(true);
2052 itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
2053 connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob);
2055 if (!collections.isEmpty()) {
2056 qCDebug(KMAIL_LOG) << "Collection dnd not supported";
2058 return true;
2062 return false;
2065 void KMComposerWin::slotPasteAsAttachment()
2067 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
2068 if (insertFromMimeData(mimeData, true)) {
2069 return;
2071 if (mimeData->hasText()) {
2072 bool ok;
2073 const QString attName = QInputDialog::getText(this,
2074 i18n("Insert clipboard text as attachment"),
2075 i18n("Name of the attachment:"), QLineEdit::Normal,
2076 QString(), &ok);
2077 if (ok) {
2078 mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
2080 return;
2084 void KMComposerWin::slotFetchJob(KJob *job)
2086 if (showErrorMessage(job)) {
2087 return;
2089 Akonadi::ItemFetchJob *fjob = qobject_cast<Akonadi::ItemFetchJob *>(job);
2090 if (!fjob) {
2091 return;
2093 const Akonadi::Item::List items = fjob->items();
2095 if (items.isEmpty()) {
2096 return;
2099 if (items.first().mimeType() == KMime::Message::mimeType()) {
2100 uint identity = 0;
2101 if (items.at(0).isValid() && items.at(0).parentCollection().isValid()) {
2102 QSharedPointer<MailCommon::FolderCollection> fd(MailCommon::FolderCollection::forCollection(items.at(0).parentCollection(), false));
2103 if (fd) {
2104 identity = fd->identity();
2107 KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this);
2108 command->start();
2109 } else {
2110 foreach (const Akonadi::Item &item, items) {
2111 QString attachmentName = QStringLiteral("attachment");
2112 if (item.hasPayload<KContacts::Addressee>()) {
2113 const KContacts::Addressee contact = item.payload<KContacts::Addressee>();
2114 attachmentName = contact.realName() + QLatin1String(".vcf");
2115 //Workaround about broken kaddressbook fields.
2116 QByteArray data = item.payloadData();
2117 KContacts::adaptIMAttributes(data);
2118 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard");
2119 } else if (item.hasPayload<KContacts::ContactGroup>()) {
2120 const KContacts::ContactGroup group = item.payload<KContacts::ContactGroup>();
2121 attachmentName = group.name() + QLatin1String(".vcf");
2122 Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(group, this);
2123 expandJob->setProperty("groupName", attachmentName);
2124 connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult);
2125 expandJob->start();
2126 } else {
2127 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1());
2133 void KMComposerWin::slotExpandGroupResult(KJob *job)
2135 Akonadi::ContactGroupExpandJob *expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job);
2136 Q_ASSERT(expandJob);
2138 const QString attachmentName = expandJob->property("groupName").toString();
2139 KContacts::VCardConverter converter;
2140 const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0);
2141 if (!groupData.isEmpty()) {
2142 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard");
2146 void KMComposerWin::slotClose()
2148 close();
2151 void KMComposerWin::slotNewComposer()
2153 KMComposerWin *win;
2154 KMime::Message::Ptr msg(new KMime::Message());
2156 MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), currentIdentity());
2157 TemplateParser::TemplateParser parser(msg, TemplateParser::TemplateParser::NewMessage);
2158 parser.setIdentityManager(KMKernel::self()->identityManager());
2159 parser.process(msg, mCollectionForNewMessage);
2160 win = new KMComposerWin(msg, false, false, KMail::Composer::New, currentIdentity());
2161 win->setCollectionForNewMessage(mCollectionForNewMessage);
2162 bool forceCursorPosition = parser.cursorPositionWasSet();
2163 if (forceCursorPosition) {
2164 win->setFocusToEditor();
2166 win->show();
2169 void KMComposerWin::slotUpdateWindowTitle()
2171 QString s(mEdtSubject->toPlainText());
2172 // Remove characters that show badly in most window decorations:
2173 // newlines tend to become boxes.
2174 if (s.isEmpty()) {
2175 setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')'));
2176 } else {
2177 setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' ')));
2181 void KMComposerWin::slotEncryptToggled(bool on)
2183 setEncryption(on, true);
2184 updateSignatureAndEncryptionStateIndicators();
2187 void KMComposerWin::setEncryption(bool encrypt, bool setByUser)
2189 bool wasModified = isModified();
2190 if (setByUser) {
2191 setModified(true);
2193 if (!mEncryptAction->isEnabled()) {
2194 encrypt = false;
2196 // check if the user wants to encrypt messages to himself and if he defined
2197 // an encryption key for the current identity
2198 else if (encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey) {
2199 if (setByUser) {
2200 KMessageBox::sorry(this,
2201 i18n("<qt><p>You have requested that messages be "
2202 "encrypted to yourself, but the currently selected "
2203 "identity does not define an (OpenPGP or S/MIME) "
2204 "encryption key to use for this.</p>"
2205 "<p>Please select the key(s) to use "
2206 "in the identity configuration.</p>"
2207 "</qt>"),
2208 i18n("Undefined Encryption Key"));
2209 setModified(wasModified);
2211 encrypt = false;
2214 // make sure the mEncryptAction is in the right state
2215 mEncryptAction->setChecked(encrypt);
2216 if (!setByUser) {
2217 updateSignatureAndEncryptionStateIndicators();
2219 // show the appropriate icon
2220 if (encrypt) {
2221 mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt")));
2222 } else {
2223 mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-decrypt")));
2226 // mark the attachments for (no) encryption
2227 if (canSignEncryptAttachments()) {
2228 mComposerBase->attachmentModel()->setEncryptSelected(encrypt);
2232 void KMComposerWin::slotSignToggled(bool on)
2234 setSigning(on, true);
2235 updateSignatureAndEncryptionStateIndicators();
2238 void KMComposerWin::setSigning(bool sign, bool setByUser)
2240 bool wasModified = isModified();
2241 if (setByUser) {
2242 setModified(true);
2244 if (!mSignAction->isEnabled()) {
2245 sign = false;
2248 // check if the user defined a signing key for the current identity
2249 if (sign && !mLastIdentityHasSigningKey) {
2250 if (setByUser) {
2251 KMessageBox::sorry(this,
2252 i18n("<qt><p>In order to be able to sign "
2253 "this message you first have to "
2254 "define the (OpenPGP or S/MIME) signing key "
2255 "to use.</p>"
2256 "<p>Please select the key to use "
2257 "in the identity configuration.</p>"
2258 "</qt>"),
2259 i18n("Undefined Signing Key"));
2260 setModified(wasModified);
2262 sign = false;
2265 // make sure the mSignAction is in the right state
2266 mSignAction->setChecked(sign);
2268 if (!setByUser) {
2269 updateSignatureAndEncryptionStateIndicators();
2271 // mark the attachments for (no) signing
2272 if (canSignEncryptAttachments()) {
2273 mComposerBase->attachmentModel()->setSignSelected(sign);
2277 void KMComposerWin::slotWordWrapToggled(bool on)
2279 if (on) {
2280 mComposerBase->editor()->enableWordWrap(validateLineWrapWidth());
2281 } else {
2282 disableWordWrap();
2286 int KMComposerWin::validateLineWrapWidth()
2288 int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
2289 if ((lineWrap == 0) || (lineWrap > 78)) {
2290 lineWrap = 78;
2291 } else if (lineWrap < 30) {
2292 lineWrap = 30;
2294 return lineWrap;
2297 void KMComposerWin::disableWordWrap()
2299 mComposerBase->editor()->disableWordWrap();
2302 void KMComposerWin::forceDisableHtml()
2304 mForceDisableHtml = true;
2305 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2306 markupAction->setEnabled(false);
2307 // FIXME: Remove the toggle toolbar action somehow
2310 bool KMComposerWin::isComposing() const
2312 return mComposerBase && mComposerBase->isComposing();
2315 void KMComposerWin::disableForgottenAttachmentsCheck()
2317 mCheckForForgottenAttachments = false;
2320 void KMComposerWin::slotPrint()
2322 printComposer(false);
2325 void KMComposerWin::slotPrintPreview()
2327 printComposer(true);
2330 void KMComposerWin::printComposer(bool preview)
2332 MessageComposer::Composer *composer = createSimpleComposer();
2333 mMiscComposers.append(composer);
2334 composer->setProperty("preview", preview);
2335 connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult);
2336 composer->start();
2339 void KMComposerWin::slotPrintComposeResult(KJob *job)
2341 const bool preview = job->property("preview").toBool();
2342 printComposeResult(job, preview);
2345 void KMComposerWin::printComposeResult(KJob *job, bool preview)
2347 Q_ASSERT(dynamic_cast< MessageComposer::Composer * >(job));
2348 MessageComposer::Composer *composer = dynamic_cast< MessageComposer::Composer * >(job);
2349 Q_ASSERT(mMiscComposers.contains(composer));
2350 mMiscComposers.removeAll(composer);
2352 if (composer->error() == MessageComposer::Composer::NoError) {
2354 Q_ASSERT(composer->resultMessages().size() == 1);
2355 Akonadi::Item printItem;
2356 printItem.setPayload<KMime::Message::Ptr>(composer->resultMessages().first());
2357 Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().first()), printItem);
2358 const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich;
2359 const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text;
2360 KMPrintCommand *command = new KMPrintCommand(this, printItem, Q_NULLPTR,
2361 format, isHtml);
2362 command->setPrintPreview(preview);
2363 command->start();
2364 } else {
2365 showErrorMessage(job);
2370 void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method,
2371 MessageComposer::MessageSender::SaveIn saveIn)
2373 // TODO integrate with MDA online status
2374 if (method == MessageComposer::MessageSender::SendImmediate) {
2375 if (!MessageComposer::Util::sendMailDispatcherIsOnline()) {
2376 method = MessageComposer::MessageSender::SendLater;
2380 if (saveIn == MessageComposer::MessageSender::SaveInNone) { // don't save as draft or template, send immediately
2381 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2382 if (!(mShowHeaders & HDR_FROM)) {
2383 mShowHeaders |= HDR_FROM;
2384 rethinkFields(false);
2386 mEdtFrom->setFocus();
2387 KMessageBox::sorry(this,
2388 i18n("You must enter your email address in the "
2389 "From: field. You should also set your email "
2390 "address for all identities, so that you do "
2391 "not have to enter it for each message."));
2392 return;
2394 if (mComposerBase->to().isEmpty()) {
2395 if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) {
2396 KMessageBox::information(this,
2397 i18n("You must specify at least one receiver, "
2398 "either in the To: field or as CC or as BCC."));
2400 return;
2401 } else {
2402 int rc = KMessageBox::questionYesNo(this,
2403 i18n("To: field is empty. "
2404 "Send message anyway?"),
2405 i18n("No To: specified"),
2406 KStandardGuiItem::yes(),
2407 KStandardGuiItem::no(),
2408 QStringLiteral(":kmail_no_to_field_specified"));
2409 if (rc == KMessageBox::No) {
2410 return;
2415 if (subject().isEmpty()) {
2416 mEdtSubject->setFocus();
2417 int rc =
2418 KMessageBox::questionYesNo(this,
2419 i18n("You did not specify a subject. "
2420 "Send message anyway?"),
2421 i18n("No Subject Specified"),
2422 KGuiItem(i18n("S&end as Is")),
2423 KGuiItem(i18n("&Specify the Subject")),
2424 QStringLiteral("no_subject_specified"));
2425 if (rc == KMessageBox::No) {
2426 return;
2430 const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment();
2431 if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) ||
2432 (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) {
2433 return;
2435 MessageComposer::PluginEditorCheckBeforeSendParams params;
2436 params.setSubject(subject());
2437 params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
2438 params.setIdentity(mComposerBase->identityCombo()->currentIdentity());
2439 params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0);
2440 params.setTransportId(mComposerBase->transportComboBox()->currentTransportId());
2441 const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(mComposerBase->identityCombo()->currentIdentity());
2442 QString defaultDomainName;
2443 if (!ident.isNull()) {
2444 defaultDomainName = ident.defaultDomainName();
2446 params.setBccAddresses(mComposerBase->bcc().trimmed());
2447 params.setToAddresses(mComposerBase->to().trimmed());
2448 params.setCcAddresses(mComposerBase->cc().trimmed());
2449 params.setDefaultDomain(defaultDomainName);
2451 if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) {
2452 return;
2454 const QStringList recipients = { mComposerBase->to().trimmed(), mComposerBase->cc().trimmed(), mComposerBase->bcc().trimmed()};
2456 setEnabled(false);
2458 // Validate the To:, CC: and BCC fields
2459 AddressValidationJob *job = new AddressValidationJob(recipients.join(QStringLiteral(", ")), this, this);
2460 job->setDefaultDomain(defaultDomainName);
2461 job->setProperty("method", static_cast<int>(method));
2462 job->setProperty("saveIn", static_cast<int>(saveIn));
2463 connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend);
2464 job->start();
2466 // we'll call send from within slotDoDelaySend
2467 } else {
2468 if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptAction->isChecked() &&
2469 !KMailSettings::self()->neverEncryptDrafts() &&
2470 mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) {
2472 KMessageBox::information(this, i18n("You must specify at least one receiver "
2473 "in order to be able to encrypt a draft.")
2475 return;
2477 doDelayedSend(method, saveIn);
2481 void KMComposerWin::slotDoDelayedSend(KJob *job)
2483 if (job->error()) {
2484 KMessageBox::error(this, job->errorText());
2485 setEnabled(true);
2486 return;
2489 const AddressValidationJob *validateJob = qobject_cast<AddressValidationJob *>(job);
2491 // Abort sending if one of the recipient addresses is invalid ...
2492 if (!validateJob->isValid()) {
2493 setEnabled(true);
2494 return;
2497 // ... otherwise continue as usual
2498 const MessageComposer::MessageSender::SendMethod method = static_cast<MessageComposer::MessageSender::SendMethod>(job->property("method").toInt());
2499 const MessageComposer::MessageSender::SaveIn saveIn = static_cast<MessageComposer::MessageSender::SaveIn>(job->property("saveIn").toInt());
2501 doDelayedSend(method, saveIn);
2504 void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase)
2507 QList< QByteArray > charsets = mCodecAction->mimeCharsets();
2508 if (!mOriginalPreferredCharset.isEmpty()) {
2509 charsets.insert(0, mOriginalPreferredCharset);
2511 mComposerBase->setFrom(from());
2512 mComposerBase->setReplyTo(replyTo());
2513 mComposerBase->setSubject(subject());
2514 mComposerBase->setCharsets(charsets);
2515 mComposerBase->setUrgent(mUrgentAction->isChecked());
2516 mComposerBase->setMDNRequested(mRequestMDNAction->isChecked());
2519 void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn)
2521 #ifndef QT_NO_CURSOR
2522 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
2523 #endif
2524 applyComposerSetting(mComposerBase);
2525 if (mForceDisableHtml) {
2526 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2528 bool sign = mSignAction->isChecked();
2529 bool encrypt = mEncryptAction->isChecked();
2531 mComposerBase->setCryptoOptions(sign, encrypt, cryptoMessageFormat(),
2532 ((saveIn != MessageComposer::MessageSender::SaveInNone && KMailSettings::self()->neverEncryptDrafts())
2533 || mSigningAndEncryptionExplicitlyDisabled));
2535 const int num = KMailSettings::self()->customMessageHeadersCount();
2536 QMap<QByteArray, QString> customHeader;
2537 for (int ix = 0; ix < num; ++ix) {
2538 CustomMimeHeader customMimeHeader(QString::number(ix));
2539 customMimeHeader.load();
2540 customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue());
2543 QMapIterator<QByteArray, QString> extraCustomHeader(mExtraHeaders);
2544 while (extraCustomHeader.hasNext()) {
2545 extraCustomHeader.next();
2546 customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value());
2549 mComposerBase->setCustomHeader(customHeader);
2550 mComposerBase->send(method, saveIn, false);
2553 void KMComposerWin::slotSendLater()
2555 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
2556 return;
2558 if (!checkRecipientNumber()) {
2559 return;
2561 if (mComposerBase->editor()->checkExternalEditorFinished()) {
2562 const bool wasRegistered = (SendLater::SendLaterUtil::sentLaterAgentWasRegistered() && SendLater::SendLaterUtil::sentLaterAgentEnabled());
2563 if (wasRegistered) {
2564 SendLater::SendLaterInfo *info = Q_NULLPTR;
2565 QPointer<SendLater::SendLaterDialog> dlg = new SendLater::SendLaterDialog(info, this);
2566 if (dlg->exec()) {
2567 info = dlg->info();
2568 const SendLater::SendLaterDialog::SendLaterAction action = dlg->action();
2569 delete dlg;
2570 switch (action) {
2571 case SendLater::SendLaterDialog::Unknown:
2572 qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it.";
2573 break;
2574 case SendLater::SendLaterDialog::Canceled:
2575 return;
2576 break;
2577 case SendLater::SendLaterDialog::PutInOutbox:
2578 doSend(MessageComposer::MessageSender::SendLater);
2579 break;
2580 case SendLater::SendLaterDialog::SendDeliveryAtTime: {
2581 mComposerBase->setSendLaterInfo(info);
2582 if (info->isRecurrence()) {
2583 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates);
2584 } else {
2585 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts);
2587 break;
2590 } else {
2591 delete dlg;
2593 } else {
2594 doSend(MessageComposer::MessageSender::SendLater);
2599 void KMComposerWin::slotSaveDraft()
2601 if (mComposerBase->editor()->checkExternalEditorFinished()) {
2602 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts);
2606 void KMComposerWin::slotSaveTemplate()
2608 if (mComposerBase->editor()->checkExternalEditorFinished()) {
2609 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates);
2613 void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport)
2615 if (transport) {
2616 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
2617 slotSendNow();
2621 void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport)
2623 if (transport) {
2624 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
2625 slotSendLater();
2629 void KMComposerWin::sendNow(bool shortcutUsed)
2631 if (!mComposerBase->editor()->checkExternalEditorFinished()) {
2632 return;
2634 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
2635 return;
2637 if (!checkRecipientNumber()) {
2638 return;
2640 mSendNowByShortcutUsed = shortcutUsed;
2641 if (KMailSettings::self()->checkSpellingBeforeSend()) {
2642 mComposerBase->editor()->forceSpellChecking();
2643 } else {
2644 slotCheckSendNow();
2648 void KMComposerWin::slotSendNowByShortcut()
2650 sendNow(true);
2653 void KMComposerWin::slotSendNow()
2655 sendNow(false);
2658 void KMComposerWin::confirmBeforeSend()
2660 const int rc = KMessageBox::warningYesNoCancel(mMainWidget,
2661 i18n("About to send email..."),
2662 i18n("Send Confirmation"),
2663 KGuiItem(i18n("&Send Now")),
2664 KGuiItem(i18n("Send &Later")));
2666 if (rc == KMessageBox::Yes) {
2667 doSend(MessageComposer::MessageSender::SendImmediate);
2668 } else if (rc == KMessageBox::No) {
2669 doSend(MessageComposer::MessageSender::SendLater);
2673 void KMComposerWin::slotCheckSendNowStep2()
2675 if (KMailSettings::self()->confirmBeforeSend()) {
2676 confirmBeforeSend();
2677 } else {
2678 if (mSendNowByShortcutUsed) {
2679 if (!KMailSettings::self()->checkSendDefaultActionShortcut()) {
2680 ValidateSendMailShortcut validateShortcut(actionCollection(), this);
2681 if (!validateShortcut.validate()) {
2682 return;
2685 if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) {
2686 confirmBeforeSend();
2687 return;
2690 doSend(MessageComposer::MessageSender::SendImmediate);
2694 void KMComposerWin::slotCheckSendNow()
2696 PotentialPhishingEmailJob *job = new PotentialPhishingEmailJob(this);
2697 KConfigGroup group(KSharedConfig::openConfig(), "PotentialPhishing");
2698 const QStringList whiteList = group.readEntry("whiteList", QStringList());
2699 job->setEmailWhiteList(whiteList);
2700 QStringList lst;
2701 lst << mComposerBase->to();
2702 if (!mComposerBase->cc().isEmpty()) {
2703 lst << mComposerBase->cc().split(QLatin1Char(','));
2705 if (!mComposerBase->bcc().isEmpty()) {
2706 lst << mComposerBase->bcc().split(QLatin1Char(','));
2708 job->setEmails(lst);
2709 connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound);
2710 job->start();
2713 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list)
2715 if (list.isEmpty()) {
2716 slotCheckSendNowStep2();
2717 } else {
2718 mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list);
2722 bool KMComposerWin::checkRecipientNumber() const
2724 const int thresHold = KMailSettings::self()->recipientThreshold();
2725 if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) {
2726 if (KMessageBox::questionYesNo(mMainWidget,
2727 i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold),
2728 i18n("Too many recipients"),
2729 KGuiItem(i18n("&Send as Is")),
2730 KGuiItem(i18n("&Edit Recipients"))) == KMessageBox::No) {
2731 return false;
2734 return true;
2737 void KMComposerWin::enableHtml()
2739 if (mForceDisableHtml) {
2740 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2741 return;
2744 mComposerBase->editor()->activateRichText();
2745 if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
2746 // Use singleshot, as we we might actually be called from a slot that wanted to disable the
2747 // toolbar (but the messagebox in disableHtml() prevented that and called us).
2748 // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
2749 // signal, so wait one event loop run for that.
2750 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show);
2752 if (!markupAction->isChecked()) {
2753 markupAction->setChecked(true);
2756 mComposerBase->editor()->composerActions()->updateActionStates();
2757 mComposerBase->editor()->composerActions()->setActionsEnabled(true);
2760 void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation)
2762 bool forcePlainTextMarkup = false;
2763 if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed() && !mForceDisableHtml) {
2764 int choice = KMessageBox::warningYesNoCancel(this, i18n("Turning HTML mode off "
2765 "will cause the text to lose the formatting. Are you sure?"),
2766 i18n("Lose the formatting?"), KGuiItem(i18n("Lose Formatting")), KGuiItem(i18n("Add Markup Plain Text")), KStandardGuiItem::cancel(),
2767 QStringLiteral("LoseFormattingWarning"));
2769 switch (choice) {
2770 case KMessageBox::Cancel:
2771 enableHtml();
2772 return;
2773 case KMessageBox::No:
2774 forcePlainTextMarkup = true;
2775 break;
2776 case KMessageBox::Yes:
2777 break;
2781 mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup);
2782 mComposerBase->editor()->switchToPlainText();
2783 mComposerBase->editor()->composerActions()->setActionsEnabled(false);
2785 slotUpdateFont();
2786 if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
2787 // See the comment in enableHtml() why we use a singleshot timer, similar situation here.
2788 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide);
2790 if (markupAction->isChecked()) {
2791 markupAction->setChecked(false);
2795 void KMComposerWin::slotToggleMarkup()
2797 htmlToolBarVisibilityChanged(markupAction->isChecked());
2800 void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode)
2802 if (mode == MessageComposer::RichTextComposerNg::Plain) {
2803 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all?
2804 } else {
2805 enableHtml();
2809 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible)
2811 if (visible) {
2812 enableHtml();
2813 } else {
2814 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
2818 void KMComposerWin::slotAutoSpellCheckingToggled(bool on)
2820 mAutoSpellCheckingAction->setChecked(on);
2821 if (on != mComposerBase->editor()->checkSpellingEnabled()) {
2822 mComposerBase->editor()->setCheckSpellingEnabled(on);
2824 if (on != mEdtSubject->checkSpellingEnabled()) {
2825 mEdtSubject->setCheckSpellingEnabled(on);
2827 mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on);
2830 void KMComposerWin::slotSpellCheckingStatus(const QString &status)
2832 mStatusBarLabelList.at(0)->setText(status);
2833 QTimer::singleShot(2000, this, &KMComposerWin::slotSpellcheckDoneClearStatus);
2836 void KMComposerWin::slotSpellcheckDoneClearStatus()
2838 mStatusBarLabelList.at(0)->clear();
2841 void KMComposerWin::slotIdentityChanged(uint uoid, bool initalChange)
2843 if (mMsg == Q_NULLPTR) {
2844 qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!";
2845 return;
2848 const KIdentityManagement::Identity &ident =
2849 KMKernel::self()->identityManager()->identityForUoid(uoid);
2850 if (ident.isNull()) {
2851 return;
2853 bool wasModified(isModified());
2854 Q_EMIT identityChanged(identity());
2855 if (!ident.fullEmailAddr().isNull()) {
2856 mEdtFrom->setText(ident.fullEmailAddr());
2859 // make sure the From field is shown if it does not contain a valid email address
2860 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2861 mShowHeaders |= HDR_FROM;
2863 if (mEdtReplyTo) {
2864 mEdtReplyTo->setText(ident.replyToAddr());
2867 // remove BCC of old identity and add BCC of new identity (if they differ)
2868 const KIdentityManagement::Identity &oldIdentity =
2869 KMKernel::self()->identityManager()->identityForUoidOrDefault(mId);
2871 if (ident.organization().isEmpty()) {
2872 mMsg->removeHeader<KMime::Headers::Organization>();
2873 } else {
2874 KMime::Headers::Organization *const organization = new KMime::Headers::Organization;
2875 organization->fromUnicodeString(ident.organization(), "utf-8");
2876 mMsg->setHeader(organization);
2878 if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) {
2879 mMsg->removeHeader("X-Face");
2880 } else {
2881 QString xface = ident.xface();
2882 if (!xface.isEmpty()) {
2883 int numNL = (xface.length() - 1) / 70;
2884 for (int i = numNL; i > 0; --i) {
2885 xface.insert(i * 70, QStringLiteral("\n\t"));
2887 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-Face");
2888 header->fromUnicodeString(xface, "utf-8");
2889 mMsg->setHeader(header);
2892 const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
2893 const Transport *transport = TransportManager::self()->transportById(transportId, true);
2894 if (!transport) {
2895 mMsg->removeHeader("X-KMail-Transport");
2896 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
2897 } else {
2898 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport");
2899 header->fromUnicodeString(QString::number(transport->id()), "utf-8");
2900 mMsg->setHeader(header);
2901 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
2904 const bool fccIsDisabled = ident.disabledFcc();
2905 if (fccIsDisabled) {
2906 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled");
2907 header->fromUnicodeString(QStringLiteral("true"), "utf-8");
2908 mMsg->setHeader(header);
2909 } else {
2910 mMsg->removeHeader("X-KMail-FccDisabled");
2912 mFccFolder->setEnabled(!fccIsDisabled);
2914 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
2915 slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary());
2916 if (!mPreventFccOverwrite) {
2917 setFcc(ident.fcc());
2919 // if unmodified, apply new template, if one is set
2920 if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) &&
2921 !initalChange) {
2922 applyTemplate(uoid, mId);
2923 } else {
2924 mComposerBase->identityChanged(ident, oldIdentity, false);
2925 mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage());
2928 // disable certain actions if there is no PGP user identity set
2929 // for this profile
2930 bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
2931 bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
2932 // save the state of the sign and encrypt button
2933 if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) {
2934 mLastEncryptActionState = mEncryptAction->isChecked();
2935 setEncryption(false);
2937 if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) {
2938 mLastSignActionState = mSignAction->isChecked();
2939 setSigning(false);
2941 // restore the last state of the sign and encrypt button
2942 if (bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey) {
2943 setEncryption(mLastEncryptActionState);
2945 if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) {
2946 setSigning(mLastSignActionState);
2949 mCryptoModuleAction->setCurrentItem(format2cb(
2950 Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat())));
2951 slotSelectCryptoModule(true);
2953 mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
2954 mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
2955 const KIdentityManagement::Signature sig = const_cast<KIdentityManagement::Identity &>(ident).signature();
2956 bool isEnabledSignature = sig.isEnabledSignature();
2957 mAppendSignature->setEnabled(isEnabledSignature);
2958 mPrependSignature->setEnabled(isEnabledSignature);
2959 mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature);
2961 mId = uoid;
2962 changeCryptoAction();
2963 // make sure the From and BCC fields are shown if necessary
2964 rethinkFields(false);
2965 setModified(wasModified);
2968 void KMComposerWin::slotSpellcheckConfig()
2970 static_cast<KMComposerEditorNg *>(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc"));
2973 void KMComposerWin::slotEditToolbars()
2975 KConfigGroup grp(KMKernel::self()->config()->group("Composer"));
2976 saveMainWindowSettings(grp);
2977 KEditToolBar dlg(guiFactory(), this);
2979 connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars);
2981 dlg.exec();
2984 void KMComposerWin::slotUpdateToolbars()
2986 createGUI(QStringLiteral("kmcomposerui.rc"));
2987 applyMainWindowSettings(KMKernel::self()->config()->group("Composer"));
2990 void KMComposerWin::slotEditKeys()
2992 KShortcutsDialog::configure(actionCollection(),
2993 KShortcutsEditor::LetterShortcutsDisallowed);
2996 void KMComposerWin::setFocusToEditor()
2998 // The cursor position is already set by setMsg(), so we only need to set the
2999 // focus here.
3000 mComposerBase->editor()->setFocus();
3003 void KMComposerWin::setFocusToSubject()
3005 mEdtSubject->setFocus();
3008 void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode)
3010 KMailSettings::self()->setCompletionMode((int) mode);
3012 // sync all the lineedits to the same completion mode
3013 mEdtFrom->setCompletionMode(mode);
3014 mEdtReplyTo->setCompletionMode(mode);
3015 mComposerBase->recipientsEditor()->setCompletionMode(mode);
3018 void KMComposerWin::slotConfigChanged()
3020 readConfig(true /*reload*/);
3021 mComposerBase->updateAutoSave();
3022 rethinkFields();
3023 slotWordWrapToggled(mWordWrapAction->isChecked());
3027 * checks if the drafts-folder has been deleted
3028 * that is not nice so we set the system-drafts-folder
3030 void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col)
3032 qCDebug(KMAIL_LOG) << "you killed me.";
3033 // TODO: need to handle templates here?
3034 if ((mFolder.isValid()) && (col.id() == mFolder.id())) {
3035 mFolder = CommonKernel->draftsCollectionFolder();
3036 qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id();
3040 void KMComposerWin::slotOverwriteModeChanged()
3042 const bool overwriteMode = mComposerBase->editor()->overwriteMode();
3043 mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1);
3044 mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode);
3047 void KMComposerWin::slotCursorPositionChanged()
3049 // Change Line/Column info in status bar
3050 const int line = mComposerBase->editor()->linePosition();
3051 const int col = mComposerBase->editor()->columnNumber();
3052 QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line + 1);
3053 mStatusBarLabelList.at(1)->setText(temp);
3054 temp = i18n(" Column: %1 ", col + 1);
3055 mStatusBarLabelList.at(2)->setText(temp);
3057 // Show link target in status bar
3058 if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) {
3059 const QString text = mComposerBase->editor()->composerControler()->currentLinkText();
3060 const QString url = mComposerBase->editor()->composerControler()->currentLinkUrl();
3061 mStatusBarLabelList.at(0)->setText(text + QLatin1String(" -> ") + url);
3062 } else {
3063 mStatusBarLabelList.at(0)->clear();
3067 void KMComposerWin::recipientEditorSizeHintChanged()
3069 QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize);
3072 void KMComposerWin::setMaximumHeaderSize()
3074 mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
3077 void KMComposerWin::updateSignatureAndEncryptionStateIndicators()
3079 mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(mSignAction->isChecked(), mEncryptAction->isChecked());
3082 void KMComposerWin::slotDictionaryLanguageChanged(const QString &language)
3084 mComposerBase->dictionary()->setCurrentByDictionary(language);
3087 void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection)
3089 mComposerBase->setFcc(collection);
3090 mComposerBase->editor()->document()->setModified(true);
3093 void KMComposerWin::slotSaveAsFile()
3095 SaveAsFileJob *job = new SaveAsFileJob(this);
3096 job->setParentWidget(this);
3097 job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
3098 job->setTextDocument(mComposerBase->editor()->document());
3099 job->start();
3100 //not necessary to delete it. It done in SaveAsFileJob
3103 void KMComposerWin::slotAttachMissingFile()
3105 mComposerBase->attachmentController()->showAddAttachmentFileDialog();
3108 void KMComposerWin::slotVerifyMissingAttachmentTimeout()
3110 if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) {
3111 mAttachmentMissing->animatedShow();
3115 void KMComposerWin::slotExplicitClosedMissingAttachment()
3117 if (m_verifyMissingAttachment) {
3118 m_verifyMissingAttachment->stop();
3119 delete m_verifyMissingAttachment;
3120 m_verifyMissingAttachment = Q_NULLPTR;
3124 void KMComposerWin::addExtraCustomHeaders(const QMap<QByteArray, QString> &headers)
3126 mExtraHeaders = headers;
3129 void KMComposerWin::slotExternalEditorStarted()
3131 mComposerBase->identityCombo()->setEnabled(false);
3132 mExternalEditorWarning->show();
3135 void KMComposerWin::slotExternalEditorClosed()
3137 mComposerBase->identityCombo()->setEnabled(true);
3138 mExternalEditorWarning->hide();
3141 void KMComposerWin::slotInsertShortUrl(const QString &url)
3143 mComposerBase->editor()->composerControler()->insertLink(url);
3146 void KMComposerWin::slotShareLinkDone(const QString &link)
3148 mComposerBase->editor()->composerControler()->insertShareLink(link);
3151 void KMComposerWin::slotTransportChanged()
3153 mComposerBase->editor()->document()->setModified(true);
3156 void KMComposerWin::slotFollowUpMail(bool toggled)
3158 if (toggled) {
3159 QPointer<MessageComposer::FollowUpReminderSelectDateDialog> dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this);
3160 if (dlg->exec()) {
3161 mComposerBase->setFollowUpDate(dlg->selectedDate());
3162 mComposerBase->setFollowUpCollection(dlg->collection());
3163 } else {
3164 mFollowUpToggleAction->setChecked(false);
3166 delete dlg;
3167 } else {
3168 mComposerBase->clearFollowUp();
3172 void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b)
3174 mSnippetWidget->setVisible(b);
3175 mSnippetSplitterCollapser->setVisible(b);
3178 void KMComposerWin::slotOverwriteModeWasChanged(bool state)
3180 mComposerBase->editor()->setCursorWidth(state ? 5 : 1);
3181 mComposerBase->editor()->setOverwriteMode(state);
3184 QList<KToggleAction *> KMComposerWin::customToolsList() const
3186 return mCustomToolsWidget->actionList();
3189 QList<QAction *> KMComposerWin::pluginToolsActionListForPopupMenu() const
3191 return mPluginEditorManagerInterface->actionsType(MessageComposer::ActionType::PopupMenu);
3194 void KMComposerWin::slotInsertNonBreakingSpace()
3196 mComposerBase->editor()->insertPlainText(QChar(0x000A0));