Fix Bug 362258 - regression: drop file in composer no longer adds attachment or asks...
[kdepim.git] / kmail / src / editor / kmcomposerwin.cpp
blob413c0660e18002f77ce5ffccac3a2794e8cd9db4
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, SLOT(slotFind()), actionCollection());
1141 mFindNextText = KStandardAction::findNext(mRichTextEditorwidget, SLOT(slotFindNext()), actionCollection());
1143 mReplaceText = KStandardAction::replace(mRichTextEditorwidget, SLOT(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);
1303 createGUI(QStringLiteral("kmcomposerui.rc"));
1304 initializePluginActions();
1305 connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled,
1306 this, &KMComposerWin::htmlToolBarVisibilityChanged);
1308 // In Kontact, this entry would read "Configure Kontact", but bring
1309 // up KMail's config dialog. That's sensible, though, so fix the label.
1310 QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure"));
1311 if (configureAction) {
1312 configureAction->setText(i18n("Configure KMail..."));
1316 void KMComposerWin::initializePluginActions()
1318 if (guiFactory()) {
1319 QHashIterator<MessageComposer::ActionType::Type, QList<QAction *> > localActionsType(mPluginEditorManagerInterface->actionsType());
1320 while (localActionsType.hasNext()) {
1321 localActionsType.next();
1322 QList<QAction *> lst = localActionsType.value();
1323 if (!lst.isEmpty()) {
1324 const QString actionlistname = QStringLiteral("kmaileditor") + MessageComposer::PluginEditorInterface::actionXmlExtension(localActionsType.key());
1325 Q_FOREACH (KXMLGUIClient *client, guiFactory()->clients()) {
1326 client->unplugActionList(actionlistname);
1327 client->plugActionList(actionlistname, lst);
1334 void KMComposerWin::changeCryptoAction()
1336 const KIdentityManagement::Identity &ident =
1337 KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity());
1339 if (!Kleo::CryptoBackendFactory::instance()->openpgp() && !Kleo::CryptoBackendFactory::instance()->smime()) {
1340 // no crypto whatsoever
1341 mEncryptAction->setEnabled(false);
1342 setEncryption(false);
1343 mSignAction->setEnabled(false);
1344 setSigning(false);
1345 } else {
1346 const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp() &&
1347 !ident.pgpSigningKey().isEmpty();
1348 const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime() &&
1349 !ident.smimeSigningKey().isEmpty();
1351 setEncryption(false);
1352 setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign());
1357 void KMComposerWin::setupStatusBar(QWidget *w)
1359 //KPIM::ProgressStatusBarWidget *progressStatusBarWidget = new KPIM::ProgressStatusBarWidget(statusBar(), this, PimCommon::StorageServiceProgressManager::progressTypeValue());
1360 statusBar()->addWidget(w);
1361 QLabel *lab = new QLabel(this);
1362 lab->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
1363 statusBar()->addPermanentWidget(lab);
1364 mStatusBarLabelList.append(lab);
1366 lab = new QLabel(this);
1367 lab->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 "
1368 , QStringLiteral(" ")));
1369 statusBar()->addPermanentWidget(lab);
1370 mStatusBarLabelList.append(lab);
1372 lab = new QLabel(i18n(" Column: %1 ", QStringLiteral(" ")));
1373 statusBar()->addPermanentWidget(lab);
1374 mStatusBarLabelList.append(lab);
1376 mStatusBarLabelToggledOverrideMode = new StatusBarLabelToggledState(this);
1377 mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS"));
1378 statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0);
1379 connect(mStatusBarLabelToggledOverrideMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotOverwriteModeWasChanged);
1381 mStatusBarLabelSpellCheckingChangeMode = new StatusBarLabelToggledState(this);
1382 mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off"));
1383 statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0);
1384 connect(mStatusBarLabelSpellCheckingChangeMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled);
1386 //statusBar()->addPermanentWidget(progressStatusBarWidget->littleProgress());
1390 void KMComposerWin::setupEditor(void)
1392 QFontMetrics fm(mBodyFont);
1393 mComposerBase->editor()->setTabStopWidth(fm.width(QLatin1Char(' ')) * 8);
1395 slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap());
1397 // Font setup
1398 slotUpdateFont();
1400 connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged,
1401 this, &KMComposerWin::slotCursorPositionChanged);
1402 slotCursorPositionChanged();
1405 QString KMComposerWin::subject() const
1407 return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText());
1410 QString KMComposerWin::from() const
1412 return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text());
1415 QString KMComposerWin::replyTo() const
1417 if (mEdtReplyTo) {
1418 return MessageComposer::Util::cleanedUpHeaderString(mEdtReplyTo->text());
1419 } else {
1420 return QString();
1424 void KMComposerWin::setCurrentTransport(int transportId)
1426 mComposerBase->transportComboBox()->setCurrentTransport(transportId);
1429 void KMComposerWin::setCurrentReplyTo(const QString &replyTo)
1431 if (mEdtReplyTo) {
1432 mEdtReplyTo->setText(replyTo);
1436 uint KMComposerWin::currentIdentity() const
1438 return mComposerBase->identityCombo()->currentIdentity();
1441 void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg, bool lastSignState, bool lastEncryptState, bool mayAutoSign,
1442 bool allowDecryption, bool isModified)
1444 if (!newMsg) {
1445 qCDebug(KMAIL_LOG) << "newMsg == 0!";
1446 return;
1449 if (lastSignState) {
1450 mLastSignActionState = true;
1453 if (lastEncryptState) {
1454 mLastEncryptActionState = true;
1457 mComposerBase->setMessage(newMsg, allowDecryption);
1458 mMsg = newMsg;
1459 KIdentityManagement::IdentityManager *im = KMKernel::self()->identityManager();
1461 mEdtFrom->setText(mMsg->from()->asUnicodeString());
1462 mEdtSubject->setText(mMsg->subject()->asUnicodeString());
1464 // Restore the quote prefix. We can't just use the global quote prefix here,
1465 // since the prefix is different for each message, it might for example depend
1466 // on the original sender in a reply.
1467 if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) {
1468 mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString());
1471 if (newMsg->headerByType("X-KMail-Identity") &&
1472 !newMsg->headerByType("X-KMail-Identity")->asUnicodeString().isEmpty()) {
1473 mId = newMsg->headerByType("X-KMail-Identity")->asUnicodeString().toUInt();
1476 // don't overwrite the header values with identity specific values
1477 disconnect(mComposerBase->identityCombo(), SIGNAL(identityChanged(uint)),
1478 this, SLOT(slotIdentityChanged(uint)));
1480 // load the mId into the gui, sticky or not, without emitting
1481 mComposerBase->identityCombo()->setCurrentIdentity(mId);
1482 connect(mComposerBase->identityCombo(), SIGNAL(identityChanged(uint)),
1483 this, SLOT(slotIdentityChanged(uint)));
1485 // manually load the identity's value into the fields; either the one from the
1486 // messge, where appropriate, or the one from the sticky identity. What's in
1487 // mId might have changed meanwhile, thus the save value
1488 slotIdentityChanged(mId, true /*initalChange*/);
1489 // Fixing the identitis with auto signing activated
1490 mLastSignActionState = mSignAction->isChecked();
1492 const KIdentityManagement::Identity &ident = im->identityForUoid(mComposerBase->identityCombo()->currentIdentity());
1494 // check for the presence of a DNT header, indicating that MDN's were requested
1495 if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) {
1496 QString mdnAddr = hdr->asUnicodeString();
1497 mRequestMDNAction->setChecked((!mdnAddr.isEmpty() &&
1498 im->thatIsMe(mdnAddr)) ||
1499 KMailSettings::self()->requestMDN());
1501 // check for presence of a priority header, indicating urgent mail:
1502 if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) {
1503 const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString();
1504 const QString priority = newMsg->headerByType("Priority")->asUnicodeString();
1505 if (xpriority == QLatin1String("2 (High)") && priority == QLatin1String("urgent")) {
1506 mUrgentAction->setChecked(true);
1510 if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) {
1511 mMsg->removeHeader("X-Face");
1512 } else {
1513 QString xface = ident.xface();
1514 if (!xface.isEmpty()) {
1515 int numNL = (xface.length() - 1) / 70;
1516 for (int i = numNL; i > 0; --i) {
1517 xface.insert(i * 70, QStringLiteral("\n\t"));
1519 auto header = new KMime::Headers::Generic("X-Face");
1520 header->fromUnicodeString(xface, "utf-8");
1521 mMsg->setHeader(header);
1525 // if these headers are present, the state of the message should be overruled
1526 if (auto hdr = mMsg->headerByType("X-KMail-SignatureActionEnabled")) {
1527 mLastSignActionState = (hdr->as7BitString(false).contains("true"));
1529 if (auto hdr = mMsg->headerByType("X-KMail-EncryptActionEnabled")) {
1530 mLastEncryptActionState = (hdr->as7BitString(false).contains("true"));
1532 if (auto hdr = mMsg->headerByType("X-KMail-CryptoMessageFormat")) {
1533 mCryptoModuleAction->setCurrentItem(format2cb(static_cast<Kleo::CryptoMessageFormat>(
1534 hdr->asUnicodeString().toInt())));
1537 mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
1538 mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
1540 if (Kleo::CryptoBackendFactory::instance()->openpgp() || Kleo::CryptoBackendFactory::instance()->smime()) {
1541 const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp() &&
1542 !ident.pgpSigningKey().isEmpty();
1543 const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime() &&
1544 !ident.smimeSigningKey().isEmpty();
1546 setEncryption(mLastEncryptActionState);
1547 setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState);
1549 updateSignatureAndEncryptionStateIndicators();
1551 QString kmailFcc;
1552 if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) {
1553 kmailFcc = hdr->asUnicodeString();
1555 if (kmailFcc.isEmpty()) {
1556 setFcc(ident.fcc());
1557 } else {
1558 setFcc(kmailFcc);
1560 if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) {
1561 const QString dictionary = hdr->asUnicodeString();
1562 if (!dictionary.isEmpty()) {
1563 mComposerBase->dictionary()->setCurrentByDictionary(dictionary);
1565 } else {
1566 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
1569 mEdtReplyTo->setText(mMsg->replyTo()->asUnicodeString());
1571 KMime::Content *msgContent = new KMime::Content;
1572 msgContent->setContent(mMsg->encodedContent());
1573 msgContent->parse();
1574 MessageViewer::EmptySource emptySource;
1575 MimeTreeParser::ObjectTreeParser otp(&emptySource); //All default are ok
1576 emptySource.setAllowDecryption(allowDecryption);
1577 otp.parseObjectTree(msgContent);
1579 bool shouldSetCharset = false;
1580 if ((mContext == Reply || mContext == ReplyToAll || mContext == Forward) && MessageComposer::MessageComposerSettings::forceReplyCharset()) {
1581 shouldSetCharset = true;
1583 if (shouldSetCharset && !otp.plainTextContentCharset().isEmpty()) {
1584 mOriginalPreferredCharset = otp.plainTextContentCharset();
1586 // always set auto charset, but prefer original when composing if force reply is set.
1587 mCodecAction->setAutoCharset();
1589 delete msgContent;
1591 if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign) {
1593 // Espen 2000-05-16
1594 // Delay the signature appending. It may start a fileseletor.
1595 // Not user friendy if this modal fileseletor opens before the
1596 // composer.
1598 if (MessageComposer::MessageComposerSettings::self()->prependSignature()) {
1599 QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
1600 } else {
1601 QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
1603 } else {
1604 mComposerBase->editor()->externalComposer()->startExternalEditor();
1607 setModified(isModified);
1609 // honor "keep reply in this folder" setting even when the identity is changed later on
1610 mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc);
1611 QTimer::singleShot(0, this, &KMComposerWin::forceAutoSaveMessage); //Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in.
1614 void KMComposerWin::setAutoSaveFileName(const QString &fileName)
1616 mComposerBase->setAutoSaveFileName(fileName);
1619 void KMComposerWin::setSigningAndEncryptionDisabled(bool v)
1621 mSigningAndEncryptionExplicitlyDisabled = v;
1624 void KMComposerWin::setFolder(const Akonadi::Collection &aFolder)
1626 mFolder = aFolder;
1629 void KMComposerWin::setFcc(const QString &idString)
1631 // check if the sent-mail folder still exists
1632 Akonadi::Collection col;
1633 if (idString.isEmpty()) {
1634 col = CommonKernel->sentCollectionFolder();
1635 } else {
1636 col = Akonadi::Collection(idString.toLongLong());
1639 mComposerBase->setFcc(col);
1640 mFccFolder->setCollection(col);
1643 bool KMComposerWin::isComposerModified() const
1645 return (mComposerBase->editor()->document()->isModified() ||
1646 mEdtFrom->isModified() ||
1647 (mEdtReplyTo && mEdtReplyTo->isModified()) ||
1648 mComposerBase->recipientsEditor()->isModified() ||
1649 mEdtSubject->document()->isModified());
1652 bool KMComposerWin::isModified() const
1654 return mWasModified || isComposerModified();
1657 void KMComposerWin::setModified(bool modified)
1659 mWasModified = modified;
1660 changeModifiedState(modified);
1663 void KMComposerWin::changeModifiedState(bool modified)
1665 mComposerBase->editor()->document()->setModified(modified);
1666 if (!modified) {
1667 mEdtFrom->setModified(false);
1668 if (mEdtReplyTo) {
1669 mEdtReplyTo->setModified(false);
1671 mComposerBase->recipientsEditor()->clearModified();
1672 mEdtSubject->document()->setModified(false);
1676 bool KMComposerWin::queryClose()
1678 if (!mComposerBase->editor()->checkExternalEditorFinished()) {
1679 return false;
1681 if (kmkernel->shuttingDown() || qApp->isSavingSession()) {
1682 return true;
1685 if (isModified()) {
1686 const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder));
1687 const QString savebut = (istemplate ?
1688 i18n("Re&save as Template") :
1689 i18n("&Save as Draft"));
1690 const QString savetext = (istemplate ?
1691 i18n("Resave this message in the Templates folder. "
1692 "It can then be used at a later time.") :
1693 i18n("Save this message in the Drafts folder. "
1694 "It can then be edited and sent at a later time."));
1696 const int rc = KMessageBox::warningYesNoCancel(this,
1697 i18n("Do you want to save the message for later or discard it?"),
1698 i18n("Close Composer"),
1699 KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext),
1700 KStandardGuiItem::discard(),
1701 KStandardGuiItem::cancel());
1702 if (rc == KMessageBox::Cancel) {
1703 return false;
1704 } else if (rc == KMessageBox::Yes) {
1705 // doSend will close the window. Just return false from this method
1706 if (istemplate) {
1707 slotSaveTemplate();
1708 } else {
1709 slotSaveDraft();
1711 return false;
1713 //else fall through: return true
1715 mComposerBase->cleanupAutoSave();
1717 if (!mMiscComposers.isEmpty()) {
1718 qCWarning(KMAIL_LOG) << "Tried to close while composer was active";
1719 return false;
1721 return true;
1724 MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment()
1726 bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning();
1728 if (!checkForForgottenAttachments) {
1729 return MessageComposer::ComposerViewBase::NoMissingAttachmentFound;
1732 mComposerBase->setSubject(subject()); //be sure the composer knows the subject
1733 MessageComposer::ComposerViewBase::MissingAttachment missingAttachments = mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords());
1735 return missingAttachments;
1738 void KMComposerWin::forceAutoSaveMessage()
1740 autoSaveMessage(true);
1743 void KMComposerWin::autoSaveMessage(bool force)
1745 if (isComposerModified() || force) {
1746 applyComposerSetting(mComposerBase);
1747 mComposerBase->saveMailSettings();
1748 mComposerBase->autoSaveMessage();
1749 if (!force) {
1750 mWasModified = true;
1751 changeModifiedState(false);
1753 } else {
1754 mComposerBase->updateAutoSave();
1758 bool KMComposerWin::encryptToSelf() const
1760 return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf();
1763 void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type)
1765 setEnabled(true);
1766 if (!msg.isEmpty()) {
1767 KMessageBox::sorry(mMainWidget, msg,
1768 (type == MessageComposer::ComposerViewBase::AutoSave) ? i18n("Autosave Message Failed") : i18n("Sending Message Failed"));
1772 void KMComposerWin::slotSendSuccessful()
1774 setModified(false);
1775 mComposerBase->cleanupAutoSave();
1776 mFolder = Akonadi::Collection(); // see dtor
1777 close();
1780 const KIdentityManagement::Identity &KMComposerWin::identity() const
1782 return KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity());
1785 Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const
1787 if (!mCryptoModuleAction) {
1788 return Kleo::AutoFormat;
1790 return cb2format(mCryptoModuleAction->currentItem());
1793 void KMComposerWin::addAttach(KMime::Content *msgPart)
1795 mComposerBase->addAttachmentPart(msgPart);
1796 setModified(true);
1799 void KMComposerWin::slotAddressBook()
1801 KRun::runCommand(QStringLiteral("kaddressbook"), window());
1804 void KMComposerWin::slotInsertFile()
1806 const QUrl u = insertFile();
1807 if (u.isEmpty()) {
1808 return;
1811 mRecentAction->addUrl(u);
1812 // Prevent race condition updating list when multiple composers are open
1814 QUrlQuery query;
1815 const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset")));
1816 QStringList urls = KMailSettings::self()->recentUrls();
1817 QStringList encodings = KMailSettings::self()->recentEncodings();
1818 // Prevent config file from growing without bound
1819 // Would be nicer to get this constant from KRecentFilesAction
1820 const int mMaxRecentFiles = 30;
1821 while (urls.count() > mMaxRecentFiles) {
1822 urls.removeLast();
1824 while (encodings.count() > mMaxRecentFiles) {
1825 encodings.removeLast();
1827 // sanity check
1828 if (urls.count() != encodings.count()) {
1829 urls.clear();
1830 encodings.clear();
1832 urls.prepend(u.toDisplayString());
1833 encodings.prepend(encoding);
1834 KMailSettings::self()->setRecentUrls(urls);
1835 KMailSettings::self()->setRecentEncodings(encodings);
1836 mRecentAction->saveEntries(KMKernel::self()->config()->group(QString()));
1838 slotInsertRecentFile(u);
1841 void KMComposerWin::slotRecentListFileClear()
1843 KSharedConfig::Ptr config = KMKernel::self()->config();
1844 KConfigGroup group(config, "Composer");
1845 group.deleteEntry("recent-urls");
1846 group.deleteEntry("recent-encodings");
1847 mRecentAction->saveEntries(config->group(QString()));
1850 void KMComposerWin::slotInsertRecentFile(const QUrl &u)
1852 if (u.fileName().isEmpty()) {
1853 return;
1856 // Get the encoding previously used when inserting this file
1857 QString encoding;
1858 const QStringList urls = KMailSettings::self()->recentUrls();
1859 const QStringList encodings = KMailSettings::self()->recentEncodings();
1860 const int index = urls.indexOf(u.toDisplayString());
1861 if (index != -1) {
1862 encoding = encodings[ index ];
1863 } else {
1864 qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; //see InsertTextFileJob
1865 return;
1868 MessageComposer::InsertTextFileJob *job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u);
1869 job->setEncoding(encoding);
1870 connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile);
1871 job->start();
1874 bool KMComposerWin::showErrorMessage(KJob *job)
1876 if (job->error()) {
1877 if (static_cast<KIO::Job *>(job)->ui()) {
1878 static_cast<KIO::Job *>(job)->ui()->showErrorMessage();
1879 } else {
1880 qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString();
1882 return true;
1884 return false;
1887 void KMComposerWin::slotInsertTextFile(KJob *job)
1889 showErrorMessage(job);
1892 void KMComposerWin::slotSelectCryptoModule(bool init)
1894 if (!init) {
1895 setModified(true);
1898 mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments());
1899 mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments());
1902 void KMComposerWin::slotUpdateFont()
1904 if (!mFixedFontAction) {
1905 return;
1907 mComposerBase->editor()->composerControler()->setFontForWholeText(mFixedFontAction->isChecked() ?
1908 mFixedFont : mBodyFont);
1911 QUrl KMComposerWin::insertFile()
1913 const KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlAndEncoding(QString(),
1914 QUrl(),
1915 QString(),
1916 this,
1917 i18nc("@title:window", "Insert File"));
1918 QUrl url;
1919 if (!result.URLs.isEmpty()) {
1920 url = result.URLs.first();
1921 if (url.isValid()) {
1922 MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding));
1925 return url;
1928 QString KMComposerWin::smartQuote(const QString &msg)
1930 return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
1933 void KMComposerWin::insertUrls(const QMimeData *source, const QList<QUrl> &urlList)
1935 QStringList urlAdded;
1936 foreach (const QUrl &url, urlList) {
1937 QString urlStr;
1938 if (url.scheme() == QLatin1String("mailto")) {
1939 urlStr = KEmailAddress::decodeMailtoUrl(url);
1940 } else {
1941 urlStr = url.toDisplayString();
1942 // Workaround #346370
1943 if (urlStr.isEmpty()) {
1944 urlStr = source->text();
1947 if (!urlAdded.contains(urlStr)) {
1948 mComposerBase->editor()->composerControler()->insertLink(urlStr);
1949 urlAdded.append(urlStr);
1955 bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment)
1957 // If this is a PNG image, either add it as an attachment or as an inline image
1958 if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) {
1959 // Get the image data before showing the dialog, since that processes events which can delete
1960 // the QMimeData object behind our back
1961 const QByteArray imageData = source->data(QStringLiteral("image/png"));
1962 if (imageData.isEmpty()) {
1963 return true;
1965 if (!forceAttachment) {
1966 if (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
1967 QImage image = qvariant_cast<QImage>(source->imageData());
1968 QFileInfo fi(source->text());
1970 QMenu menu;
1971 const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image"));
1972 /*const QAction *addAsAttachmentAction = */menu.addAction(i18n("Add as &Attachment"));
1973 const QAction *selectedAction = menu.exec(QCursor::pos());
1974 if (selectedAction == addAsInlineImageAction) {
1975 // Let the textedit from kdepimlibs handle inline images
1976 mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi);
1977 return true;
1978 } else if (!selectedAction) {
1979 return true;
1981 // else fall through
1984 // Ok, when we reached this point, the user wants to add the image as an attachment.
1985 // Ask for the filename first.
1986 bool ok;
1987 const QString attName =
1988 QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
1989 if (!ok) {
1990 return true;
1992 addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png");
1993 return true;
1996 // If this is a URL list, add those files as attachments or text
1997 // but do not offer this if we are pasting plain text containing an url, e.g. from a browser
1998 const QList<QUrl> urlList = source->urls();
1999 if (!urlList.isEmpty()) {
2001 //Search if it's message items.
2002 Akonadi::Item::List items;
2003 Akonadi::Collection::List collections;
2004 bool allLocalURLs = true;
2006 foreach (const QUrl &url, urlList) {
2007 if (!url.isLocalFile()) {
2008 allLocalURLs = false;
2010 const Akonadi::Item item = Akonadi::Item::fromUrl(url);
2011 if (item.isValid()) {
2012 items << item;
2013 } else {
2014 const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url);
2015 if (collection.isValid()) {
2016 collections << collection;
2021 if (items.isEmpty() && collections.isEmpty()) {
2022 if (allLocalURLs || forceAttachment) {
2023 foreach (const QUrl &url, urlList) {
2024 addAttachment(url, QString());
2026 } else {
2027 QMenu p;
2028 const int sizeUrl(urlList.size());
2029 const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl));
2030 const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl));
2031 const QAction *selectedAction = p.exec(QCursor::pos());
2033 if (selectedAction == addAsTextAction) {
2034 insertUrls(source, urlList);
2035 } else if (selectedAction == addAsAttachmentAction) {
2036 foreach (const QUrl &url, urlList) {
2037 if (url.isValid()) {
2038 addAttachment(url, QString());
2043 return true;
2044 } else {
2045 if (!items.isEmpty()) {
2046 Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(items, this);
2047 itemFetchJob->fetchScope().fetchFullPayload(true);
2048 itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
2049 connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob);
2051 if (!collections.isEmpty()) {
2052 qCDebug(KMAIL_LOG) << "Collection dnd not supported";
2054 return true;
2058 return false;
2061 void KMComposerWin::slotPasteAsAttachment()
2063 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
2064 if (insertFromMimeData(mimeData, true)) {
2065 return;
2067 if (mimeData->hasText()) {
2068 bool ok;
2069 const QString attName = QInputDialog::getText(this,
2070 i18n("Insert clipboard text as attachment"),
2071 i18n("Name of the attachment:"), QLineEdit::Normal,
2072 QString(), &ok);
2073 if (ok) {
2074 mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
2076 return;
2080 void KMComposerWin::slotFetchJob(KJob *job)
2082 if (showErrorMessage(job)) {
2083 return;
2085 Akonadi::ItemFetchJob *fjob = qobject_cast<Akonadi::ItemFetchJob *>(job);
2086 if (!fjob) {
2087 return;
2089 const Akonadi::Item::List items = fjob->items();
2091 if (items.isEmpty()) {
2092 return;
2095 if (items.first().mimeType() == KMime::Message::mimeType()) {
2096 uint identity = 0;
2097 if (items.at(0).isValid() && items.at(0).parentCollection().isValid()) {
2098 QSharedPointer<MailCommon::FolderCollection> fd(MailCommon::FolderCollection::forCollection(items.at(0).parentCollection(), false));
2099 if (fd) {
2100 identity = fd->identity();
2103 KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this);
2104 command->start();
2105 } else {
2106 foreach (const Akonadi::Item &item, items) {
2107 QString attachmentName = QStringLiteral("attachment");
2108 if (item.hasPayload<KContacts::Addressee>()) {
2109 const KContacts::Addressee contact = item.payload<KContacts::Addressee>();
2110 attachmentName = contact.realName() + QLatin1String(".vcf");
2111 //Workaround about broken kaddressbook fields.
2112 QByteArray data = item.payloadData();
2113 KContacts::adaptIMAttributes(data);
2114 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard");
2115 } else if (item.hasPayload<KContacts::ContactGroup>()) {
2116 const KContacts::ContactGroup group = item.payload<KContacts::ContactGroup>();
2117 attachmentName = group.name() + QLatin1String(".vcf");
2118 Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(group, this);
2119 expandJob->setProperty("groupName", attachmentName);
2120 connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult);
2121 expandJob->start();
2122 } else {
2123 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1());
2129 void KMComposerWin::slotExpandGroupResult(KJob *job)
2131 Akonadi::ContactGroupExpandJob *expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job);
2132 Q_ASSERT(expandJob);
2134 const QString attachmentName = expandJob->property("groupName").toString();
2135 KContacts::VCardConverter converter;
2136 const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0);
2137 if (!groupData.isEmpty()) {
2138 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard");
2142 void KMComposerWin::slotClose()
2144 close();
2147 void KMComposerWin::slotNewComposer()
2149 KMComposerWin *win;
2150 KMime::Message::Ptr msg(new KMime::Message());
2152 MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), currentIdentity());
2153 TemplateParser::TemplateParser parser(msg, TemplateParser::TemplateParser::NewMessage);
2154 parser.setIdentityManager(KMKernel::self()->identityManager());
2155 parser.process(msg, mCollectionForNewMessage);
2156 win = new KMComposerWin(msg, false, false, KMail::Composer::New, currentIdentity());
2157 win->setCollectionForNewMessage(mCollectionForNewMessage);
2158 bool forceCursorPosition = parser.cursorPositionWasSet();
2159 if (forceCursorPosition) {
2160 win->setFocusToEditor();
2162 win->show();
2165 void KMComposerWin::slotUpdateWindowTitle()
2167 QString s(mEdtSubject->toPlainText());
2168 // Remove characters that show badly in most window decorations:
2169 // newlines tend to become boxes.
2170 if (s.isEmpty()) {
2171 setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')'));
2172 } else {
2173 setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' ')));
2177 void KMComposerWin::slotEncryptToggled(bool on)
2179 setEncryption(on, true);
2180 updateSignatureAndEncryptionStateIndicators();
2183 void KMComposerWin::setEncryption(bool encrypt, bool setByUser)
2185 bool wasModified = isModified();
2186 if (setByUser) {
2187 setModified(true);
2189 if (!mEncryptAction->isEnabled()) {
2190 encrypt = false;
2192 // check if the user wants to encrypt messages to himself and if he defined
2193 // an encryption key for the current identity
2194 else if (encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey) {
2195 if (setByUser) {
2196 KMessageBox::sorry(this,
2197 i18n("<qt><p>You have requested that messages be "
2198 "encrypted to yourself, but the currently selected "
2199 "identity does not define an (OpenPGP or S/MIME) "
2200 "encryption key to use for this.</p>"
2201 "<p>Please select the key(s) to use "
2202 "in the identity configuration.</p>"
2203 "</qt>"),
2204 i18n("Undefined Encryption Key"));
2205 setModified(wasModified);
2207 encrypt = false;
2210 // make sure the mEncryptAction is in the right state
2211 mEncryptAction->setChecked(encrypt);
2212 if (!setByUser) {
2213 updateSignatureAndEncryptionStateIndicators();
2215 // show the appropriate icon
2216 if (encrypt) {
2217 mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt")));
2218 } else {
2219 mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-decrypt")));
2222 // mark the attachments for (no) encryption
2223 if (canSignEncryptAttachments()) {
2224 mComposerBase->attachmentModel()->setEncryptSelected(encrypt);
2228 void KMComposerWin::slotSignToggled(bool on)
2230 setSigning(on, true);
2231 updateSignatureAndEncryptionStateIndicators();
2234 void KMComposerWin::setSigning(bool sign, bool setByUser)
2236 bool wasModified = isModified();
2237 if (setByUser) {
2238 setModified(true);
2240 if (!mSignAction->isEnabled()) {
2241 sign = false;
2244 // check if the user defined a signing key for the current identity
2245 if (sign && !mLastIdentityHasSigningKey) {
2246 if (setByUser) {
2247 KMessageBox::sorry(this,
2248 i18n("<qt><p>In order to be able to sign "
2249 "this message you first have to "
2250 "define the (OpenPGP or S/MIME) signing key "
2251 "to use.</p>"
2252 "<p>Please select the key to use "
2253 "in the identity configuration.</p>"
2254 "</qt>"),
2255 i18n("Undefined Signing Key"));
2256 setModified(wasModified);
2258 sign = false;
2261 // make sure the mSignAction is in the right state
2262 mSignAction->setChecked(sign);
2264 if (!setByUser) {
2265 updateSignatureAndEncryptionStateIndicators();
2267 // mark the attachments for (no) signing
2268 if (canSignEncryptAttachments()) {
2269 mComposerBase->attachmentModel()->setSignSelected(sign);
2273 void KMComposerWin::slotWordWrapToggled(bool on)
2275 if (on) {
2276 mComposerBase->editor()->enableWordWrap(validateLineWrapWidth());
2277 } else {
2278 disableWordWrap();
2282 int KMComposerWin::validateLineWrapWidth()
2284 int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
2285 if ((lineWrap == 0) || (lineWrap > 78)) {
2286 lineWrap = 78;
2287 } else if (lineWrap < 30) {
2288 lineWrap = 30;
2290 return lineWrap;
2293 void KMComposerWin::disableWordWrap()
2295 mComposerBase->editor()->disableWordWrap();
2298 void KMComposerWin::forceDisableHtml()
2300 mForceDisableHtml = true;
2301 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2302 markupAction->setEnabled(false);
2303 // FIXME: Remove the toggle toolbar action somehow
2306 bool KMComposerWin::isComposing() const
2308 return mComposerBase && mComposerBase->isComposing();
2311 void KMComposerWin::disableForgottenAttachmentsCheck()
2313 mCheckForForgottenAttachments = false;
2316 void KMComposerWin::slotPrint()
2318 printComposer(false);
2321 void KMComposerWin::slotPrintPreview()
2323 printComposer(true);
2326 void KMComposerWin::printComposer(bool preview)
2328 MessageComposer::Composer *composer = createSimpleComposer();
2329 mMiscComposers.append(composer);
2330 composer->setProperty("preview", preview);
2331 connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult);
2332 composer->start();
2335 void KMComposerWin::slotPrintComposeResult(KJob *job)
2337 const bool preview = job->property("preview").toBool();
2338 printComposeResult(job, preview);
2341 void KMComposerWin::printComposeResult(KJob *job, bool preview)
2343 Q_ASSERT(dynamic_cast< MessageComposer::Composer * >(job));
2344 MessageComposer::Composer *composer = dynamic_cast< MessageComposer::Composer * >(job);
2345 Q_ASSERT(mMiscComposers.contains(composer));
2346 mMiscComposers.removeAll(composer);
2348 if (composer->error() == MessageComposer::Composer::NoError) {
2350 Q_ASSERT(composer->resultMessages().size() == 1);
2351 Akonadi::Item printItem;
2352 printItem.setPayload<KMime::Message::Ptr>(composer->resultMessages().first());
2353 Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().first()), printItem);
2354 const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich;
2355 const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text;
2356 KMPrintCommand *command = new KMPrintCommand(this, printItem, Q_NULLPTR,
2357 format, isHtml);
2358 command->setPrintPreview(preview);
2359 command->start();
2360 } else {
2361 showErrorMessage(job);
2366 void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method,
2367 MessageComposer::MessageSender::SaveIn saveIn)
2369 // TODO integrate with MDA online status
2370 if (method == MessageComposer::MessageSender::SendImmediate) {
2371 if (!MessageComposer::Util::sendMailDispatcherIsOnline()) {
2372 method = MessageComposer::MessageSender::SendLater;
2376 if (saveIn == MessageComposer::MessageSender::SaveInNone) { // don't save as draft or template, send immediately
2377 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2378 if (!(mShowHeaders & HDR_FROM)) {
2379 mShowHeaders |= HDR_FROM;
2380 rethinkFields(false);
2382 mEdtFrom->setFocus();
2383 KMessageBox::sorry(this,
2384 i18n("You must enter your email address in the "
2385 "From: field. You should also set your email "
2386 "address for all identities, so that you do "
2387 "not have to enter it for each message."));
2388 return;
2390 if (mComposerBase->to().isEmpty()) {
2391 if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) {
2392 KMessageBox::information(this,
2393 i18n("You must specify at least one receiver, "
2394 "either in the To: field or as CC or as BCC."));
2396 return;
2397 } else {
2398 int rc = KMessageBox::questionYesNo(this,
2399 i18n("To: field is empty. "
2400 "Send message anyway?"),
2401 i18n("No To: specified"),
2402 KStandardGuiItem::yes(),
2403 KStandardGuiItem::no(),
2404 QStringLiteral(":kmail_no_to_field_specified"));
2405 if (rc == KMessageBox::No) {
2406 return;
2411 if (subject().isEmpty()) {
2412 mEdtSubject->setFocus();
2413 int rc =
2414 KMessageBox::questionYesNo(this,
2415 i18n("You did not specify a subject. "
2416 "Send message anyway?"),
2417 i18n("No Subject Specified"),
2418 KGuiItem(i18n("S&end as Is")),
2419 KGuiItem(i18n("&Specify the Subject")),
2420 QStringLiteral("no_subject_specified"));
2421 if (rc == KMessageBox::No) {
2422 return;
2426 const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment();
2427 if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) ||
2428 (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) {
2429 return;
2431 MessageComposer::PluginEditorCheckBeforeSendParams params;
2432 params.setSubject(subject());
2433 params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
2434 params.setIdentity(mComposerBase->identityCombo()->currentIdentity());
2435 params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0);
2436 params.setTransportId(mComposerBase->transportComboBox()->currentTransportId());
2437 const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(mComposerBase->identityCombo()->currentIdentity());
2438 QString defaultDomainName;
2439 if (!ident.isNull()) {
2440 defaultDomainName = ident.defaultDomainName();
2442 params.setBccAddresses(mComposerBase->bcc().trimmed());
2443 params.setToAddresses(mComposerBase->to().trimmed());
2444 params.setCcAddresses(mComposerBase->cc().trimmed());
2445 params.setDefaultDomain(defaultDomainName);
2447 if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) {
2448 return;
2450 const QStringList recipients = { mComposerBase->to().trimmed(), mComposerBase->cc().trimmed(), mComposerBase->bcc().trimmed()};
2452 setEnabled(false);
2454 // Validate the To:, CC: and BCC fields
2455 AddressValidationJob *job = new AddressValidationJob(recipients.join(QStringLiteral(", ")), this, this);
2456 job->setDefaultDomain(defaultDomainName);
2457 job->setProperty("method", static_cast<int>(method));
2458 job->setProperty("saveIn", static_cast<int>(saveIn));
2459 connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend);
2460 job->start();
2462 // we'll call send from within slotDoDelaySend
2463 } else {
2464 if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptAction->isChecked() &&
2465 !KMailSettings::self()->neverEncryptDrafts() &&
2466 mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) {
2468 KMessageBox::information(this, i18n("You must specify at least one receiver "
2469 "in order to be able to encrypt a draft.")
2471 return;
2473 doDelayedSend(method, saveIn);
2477 void KMComposerWin::slotDoDelayedSend(KJob *job)
2479 if (job->error()) {
2480 KMessageBox::error(this, job->errorText());
2481 setEnabled(true);
2482 return;
2485 const AddressValidationJob *validateJob = qobject_cast<AddressValidationJob *>(job);
2487 // Abort sending if one of the recipient addresses is invalid ...
2488 if (!validateJob->isValid()) {
2489 setEnabled(true);
2490 return;
2493 // ... otherwise continue as usual
2494 const MessageComposer::MessageSender::SendMethod method = static_cast<MessageComposer::MessageSender::SendMethod>(job->property("method").toInt());
2495 const MessageComposer::MessageSender::SaveIn saveIn = static_cast<MessageComposer::MessageSender::SaveIn>(job->property("saveIn").toInt());
2497 doDelayedSend(method, saveIn);
2500 void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase)
2503 QList< QByteArray > charsets = mCodecAction->mimeCharsets();
2504 if (!mOriginalPreferredCharset.isEmpty()) {
2505 charsets.insert(0, mOriginalPreferredCharset);
2507 mComposerBase->setFrom(from());
2508 mComposerBase->setReplyTo(replyTo());
2509 mComposerBase->setSubject(subject());
2510 mComposerBase->setCharsets(charsets);
2511 mComposerBase->setUrgent(mUrgentAction->isChecked());
2512 mComposerBase->setMDNRequested(mRequestMDNAction->isChecked());
2515 void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn)
2517 #ifndef QT_NO_CURSOR
2518 KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
2519 #endif
2520 applyComposerSetting(mComposerBase);
2521 if (mForceDisableHtml) {
2522 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2524 bool sign = mSignAction->isChecked();
2525 bool encrypt = mEncryptAction->isChecked();
2527 mComposerBase->setCryptoOptions(sign, encrypt, cryptoMessageFormat(),
2528 ((saveIn != MessageComposer::MessageSender::SaveInNone && KMailSettings::self()->neverEncryptDrafts())
2529 || mSigningAndEncryptionExplicitlyDisabled));
2531 const int num = KMailSettings::self()->customMessageHeadersCount();
2532 QMap<QByteArray, QString> customHeader;
2533 for (int ix = 0; ix < num; ++ix) {
2534 CustomMimeHeader customMimeHeader(QString::number(ix));
2535 customMimeHeader.load();
2536 customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue());
2539 QMapIterator<QByteArray, QString> extraCustomHeader(mExtraHeaders);
2540 while (extraCustomHeader.hasNext()) {
2541 extraCustomHeader.next();
2542 customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value());
2545 mComposerBase->setCustomHeader(customHeader);
2546 mComposerBase->send(method, saveIn, false);
2549 void KMComposerWin::slotSendLater()
2551 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
2552 return;
2554 if (!checkRecipientNumber()) {
2555 return;
2557 if (mComposerBase->editor()->checkExternalEditorFinished()) {
2558 const bool wasRegistered = (SendLater::SendLaterUtil::sentLaterAgentWasRegistered() && SendLater::SendLaterUtil::sentLaterAgentEnabled());
2559 if (wasRegistered) {
2560 SendLater::SendLaterInfo *info = Q_NULLPTR;
2561 QPointer<SendLater::SendLaterDialog> dlg = new SendLater::SendLaterDialog(info, this);
2562 if (dlg->exec()) {
2563 info = dlg->info();
2564 const SendLater::SendLaterDialog::SendLaterAction action = dlg->action();
2565 delete dlg;
2566 switch (action) {
2567 case SendLater::SendLaterDialog::Unknown:
2568 qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it.";
2569 break;
2570 case SendLater::SendLaterDialog::Canceled:
2571 return;
2572 break;
2573 case SendLater::SendLaterDialog::PutInOutbox:
2574 doSend(MessageComposer::MessageSender::SendLater);
2575 break;
2576 case SendLater::SendLaterDialog::SendDeliveryAtTime: {
2577 mComposerBase->setSendLaterInfo(info);
2578 if (info->isRecurrence()) {
2579 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates);
2580 } else {
2581 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts);
2583 break;
2586 } else {
2587 delete dlg;
2589 } else {
2590 doSend(MessageComposer::MessageSender::SendLater);
2595 void KMComposerWin::slotSaveDraft()
2597 if (mComposerBase->editor()->checkExternalEditorFinished()) {
2598 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts);
2602 void KMComposerWin::slotSaveTemplate()
2604 if (mComposerBase->editor()->checkExternalEditorFinished()) {
2605 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates);
2609 void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport)
2611 if (transport) {
2612 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
2613 slotSendNow();
2617 void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport)
2619 if (transport) {
2620 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
2621 slotSendLater();
2625 void KMComposerWin::sendNow(bool shortcutUsed)
2627 if (!mComposerBase->editor()->checkExternalEditorFinished()) {
2628 return;
2630 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) {
2631 return;
2633 if (!checkRecipientNumber()) {
2634 return;
2636 mSendNowByShortcutUsed = shortcutUsed;
2637 if (KMailSettings::self()->checkSpellingBeforeSend()) {
2638 mComposerBase->editor()->forceSpellChecking();
2639 } else {
2640 slotCheckSendNow();
2644 void KMComposerWin::slotSendNowByShortcut()
2646 sendNow(true);
2649 void KMComposerWin::slotSendNow()
2651 sendNow(false);
2654 void KMComposerWin::confirmBeforeSend()
2656 const int rc = KMessageBox::warningYesNoCancel(mMainWidget,
2657 i18n("About to send email..."),
2658 i18n("Send Confirmation"),
2659 KGuiItem(i18n("&Send Now")),
2660 KGuiItem(i18n("Send &Later")));
2662 if (rc == KMessageBox::Yes) {
2663 doSend(MessageComposer::MessageSender::SendImmediate);
2664 } else if (rc == KMessageBox::No) {
2665 doSend(MessageComposer::MessageSender::SendLater);
2669 void KMComposerWin::slotCheckSendNowStep2()
2671 if (KMailSettings::self()->confirmBeforeSend()) {
2672 confirmBeforeSend();
2673 } else {
2674 if (mSendNowByShortcutUsed) {
2675 if (!KMailSettings::self()->checkSendDefaultActionShortcut()) {
2676 ValidateSendMailShortcut validateShortcut(actionCollection(), this);
2677 if (!validateShortcut.validate()) {
2678 return;
2681 if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) {
2682 confirmBeforeSend();
2683 return;
2686 doSend(MessageComposer::MessageSender::SendImmediate);
2690 void KMComposerWin::slotCheckSendNow()
2692 PotentialPhishingEmailJob *job = new PotentialPhishingEmailJob(this);
2693 KConfigGroup group(KSharedConfig::openConfig(), "PotentialPhishing");
2694 const QStringList whiteList = group.readEntry("whiteList", QStringList());
2695 job->setEmailWhiteList(whiteList);
2696 QStringList lst;
2697 lst << mComposerBase->to();
2698 if (!mComposerBase->cc().isEmpty()) {
2699 lst << mComposerBase->cc().split(QLatin1Char(','));
2701 if (!mComposerBase->bcc().isEmpty()) {
2702 lst << mComposerBase->bcc().split(QLatin1Char(','));
2704 job->setEmails(lst);
2705 connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound);
2706 job->start();
2709 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list)
2711 if (list.isEmpty()) {
2712 slotCheckSendNowStep2();
2713 } else {
2714 mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list);
2718 bool KMComposerWin::checkRecipientNumber() const
2720 const int thresHold = KMailSettings::self()->recipientThreshold();
2721 if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) {
2722 if (KMessageBox::questionYesNo(mMainWidget,
2723 i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold),
2724 i18n("Too many recipients"),
2725 KGuiItem(i18n("&Send as Is")),
2726 KGuiItem(i18n("&Edit Recipients"))) == KMessageBox::No) {
2727 return false;
2730 return true;
2733 void KMComposerWin::enableHtml()
2735 if (mForceDisableHtml) {
2736 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded);
2737 return;
2740 mComposerBase->editor()->activateRichText();
2741 if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
2742 // Use singleshot, as we we might actually be called from a slot that wanted to disable the
2743 // toolbar (but the messagebox in disableHtml() prevented that and called us).
2744 // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
2745 // signal, so wait one event loop run for that.
2746 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show);
2748 if (!markupAction->isChecked()) {
2749 markupAction->setChecked(true);
2752 mComposerBase->editor()->composerActions()->updateActionStates();
2753 mComposerBase->editor()->composerActions()->setActionsEnabled(true);
2756 void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation)
2758 bool forcePlainTextMarkup = false;
2759 if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed() && !mForceDisableHtml) {
2760 int choice = KMessageBox::warningYesNoCancel(this, i18n("Turning HTML mode off "
2761 "will cause the text to lose the formatting. Are you sure?"),
2762 i18n("Lose the formatting?"), KGuiItem(i18n("Lose Formatting")), KGuiItem(i18n("Add Markup Plain Text")), KStandardGuiItem::cancel(),
2763 QStringLiteral("LoseFormattingWarning"));
2765 switch (choice) {
2766 case KMessageBox::Cancel:
2767 enableHtml();
2768 return;
2769 case KMessageBox::No:
2770 forcePlainTextMarkup = true;
2771 break;
2772 case KMessageBox::Yes:
2773 break;
2777 mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup);
2778 mComposerBase->editor()->switchToPlainText();
2779 mComposerBase->editor()->composerActions()->setActionsEnabled(false);
2781 slotUpdateFont();
2782 if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
2783 // See the comment in enableHtml() why we use a singleshot timer, similar situation here.
2784 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide);
2786 if (markupAction->isChecked()) {
2787 markupAction->setChecked(false);
2791 void KMComposerWin::slotToggleMarkup()
2793 htmlToolBarVisibilityChanged(markupAction->isChecked());
2796 void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode)
2798 if (mode == MessageComposer::RichTextComposerNg::Plain) {
2799 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all?
2800 } else {
2801 enableHtml();
2805 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible)
2807 if (visible) {
2808 enableHtml();
2809 } else {
2810 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm);
2814 void KMComposerWin::slotAutoSpellCheckingToggled(bool on)
2816 mAutoSpellCheckingAction->setChecked(on);
2817 if (on != mComposerBase->editor()->checkSpellingEnabled()) {
2818 mComposerBase->editor()->setCheckSpellingEnabled(on);
2820 if (on != mEdtSubject->checkSpellingEnabled()) {
2821 mEdtSubject->setCheckSpellingEnabled(on);
2823 mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on);
2826 void KMComposerWin::slotSpellCheckingStatus(const QString &status)
2828 mStatusBarLabelList.at(0)->setText(status);
2829 QTimer::singleShot(2000, this, &KMComposerWin::slotSpellcheckDoneClearStatus);
2832 void KMComposerWin::slotSpellcheckDoneClearStatus()
2834 mStatusBarLabelList.at(0)->clear();
2837 void KMComposerWin::slotIdentityChanged(uint uoid, bool initalChange)
2839 if (mMsg == Q_NULLPTR) {
2840 qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!";
2841 return;
2844 const KIdentityManagement::Identity &ident =
2845 KMKernel::self()->identityManager()->identityForUoid(uoid);
2846 if (ident.isNull()) {
2847 return;
2849 bool wasModified(isModified());
2850 Q_EMIT identityChanged(identity());
2851 if (!ident.fullEmailAddr().isNull()) {
2852 mEdtFrom->setText(ident.fullEmailAddr());
2855 // make sure the From field is shown if it does not contain a valid email address
2856 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2857 mShowHeaders |= HDR_FROM;
2859 if (mEdtReplyTo) {
2860 mEdtReplyTo->setText(ident.replyToAddr());
2863 // remove BCC of old identity and add BCC of new identity (if they differ)
2864 const KIdentityManagement::Identity &oldIdentity =
2865 KMKernel::self()->identityManager()->identityForUoidOrDefault(mId);
2867 if (ident.organization().isEmpty()) {
2868 mMsg->removeHeader<KMime::Headers::Organization>();
2869 } else {
2870 KMime::Headers::Organization *const organization = new KMime::Headers::Organization;
2871 organization->fromUnicodeString(ident.organization(), "utf-8");
2872 mMsg->setHeader(organization);
2874 if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) {
2875 mMsg->removeHeader("X-Face");
2876 } else {
2877 QString xface = ident.xface();
2878 if (!xface.isEmpty()) {
2879 int numNL = (xface.length() - 1) / 70;
2880 for (int i = numNL; i > 0; --i) {
2881 xface.insert(i * 70, QStringLiteral("\n\t"));
2883 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-Face");
2884 header->fromUnicodeString(xface, "utf-8");
2885 mMsg->setHeader(header);
2888 const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt();
2889 const Transport *transport = TransportManager::self()->transportById(transportId, true);
2890 if (!transport) {
2891 mMsg->removeHeader("X-KMail-Transport");
2892 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
2893 } else {
2894 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport");
2895 header->fromUnicodeString(QString::number(transport->id()), "utf-8");
2896 mMsg->setHeader(header);
2897 mComposerBase->transportComboBox()->setCurrentTransport(transport->id());
2900 const bool fccIsDisabled = ident.disabledFcc();
2901 if (fccIsDisabled) {
2902 KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled");
2903 header->fromUnicodeString(QStringLiteral("true"), "utf-8");
2904 mMsg->setHeader(header);
2905 } else {
2906 mMsg->removeHeader("X-KMail-FccDisabled");
2908 mFccFolder->setEnabled(!fccIsDisabled);
2910 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary());
2911 slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary());
2912 if (!mPreventFccOverwrite) {
2913 setFcc(ident.fcc());
2915 // if unmodified, apply new template, if one is set
2916 if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) &&
2917 !initalChange) {
2918 applyTemplate(uoid, mId);
2919 } else {
2920 mComposerBase->identityChanged(ident, oldIdentity, false);
2921 mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage());
2924 // disable certain actions if there is no PGP user identity set
2925 // for this profile
2926 bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
2927 bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
2928 // save the state of the sign and encrypt button
2929 if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) {
2930 mLastEncryptActionState = mEncryptAction->isChecked();
2931 setEncryption(false);
2933 if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) {
2934 mLastSignActionState = mSignAction->isChecked();
2935 setSigning(false);
2937 // restore the last state of the sign and encrypt button
2938 if (bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey) {
2939 setEncryption(mLastEncryptActionState);
2941 if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) {
2942 setSigning(mLastSignActionState);
2945 mCryptoModuleAction->setCurrentItem(format2cb(
2946 Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat())));
2947 slotSelectCryptoModule(true);
2949 mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
2950 mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
2951 const KIdentityManagement::Signature sig = const_cast<KIdentityManagement::Identity &>(ident).signature();
2952 bool isEnabledSignature = sig.isEnabledSignature();
2953 mAppendSignature->setEnabled(isEnabledSignature);
2954 mPrependSignature->setEnabled(isEnabledSignature);
2955 mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature);
2957 mId = uoid;
2958 changeCryptoAction();
2959 // make sure the From and BCC fields are shown if necessary
2960 rethinkFields(false);
2961 setModified(wasModified);
2964 void KMComposerWin::slotSpellcheckConfig()
2966 static_cast<KMComposerEditorNg *>(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc"));
2969 void KMComposerWin::slotEditToolbars()
2971 KConfigGroup grp(KMKernel::self()->config()->group("Composer"));
2972 saveMainWindowSettings(grp);
2973 KEditToolBar dlg(guiFactory(), this);
2975 connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars);
2977 dlg.exec();
2980 void KMComposerWin::slotUpdateToolbars()
2982 createGUI(QStringLiteral("kmcomposerui.rc"));
2983 applyMainWindowSettings(KMKernel::self()->config()->group("Composer"));
2986 void KMComposerWin::slotEditKeys()
2988 KShortcutsDialog::configure(actionCollection(),
2989 KShortcutsEditor::LetterShortcutsDisallowed);
2992 void KMComposerWin::setFocusToEditor()
2994 // The cursor position is already set by setMsg(), so we only need to set the
2995 // focus here.
2996 mComposerBase->editor()->setFocus();
2999 void KMComposerWin::setFocusToSubject()
3001 mEdtSubject->setFocus();
3004 void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode)
3006 KMailSettings::self()->setCompletionMode((int) mode);
3008 // sync all the lineedits to the same completion mode
3009 mEdtFrom->setCompletionMode(mode);
3010 mEdtReplyTo->setCompletionMode(mode);
3011 mComposerBase->recipientsEditor()->setCompletionMode(mode);
3014 void KMComposerWin::slotConfigChanged()
3016 readConfig(true /*reload*/);
3017 mComposerBase->updateAutoSave();
3018 rethinkFields();
3019 slotWordWrapToggled(mWordWrapAction->isChecked());
3023 * checks if the drafts-folder has been deleted
3024 * that is not nice so we set the system-drafts-folder
3026 void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col)
3028 qCDebug(KMAIL_LOG) << "you killed me.";
3029 // TODO: need to handle templates here?
3030 if ((mFolder.isValid()) && (col.id() == mFolder.id())) {
3031 mFolder = CommonKernel->draftsCollectionFolder();
3032 qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id();
3036 void KMComposerWin::slotOverwriteModeChanged()
3038 const bool overwriteMode = mComposerBase->editor()->overwriteMode();
3039 mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1);
3040 mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode);
3043 void KMComposerWin::slotCursorPositionChanged()
3045 // Change Line/Column info in status bar
3046 const int line = mComposerBase->editor()->linePosition();
3047 const int col = mComposerBase->editor()->columnNumber();
3048 QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line + 1);
3049 mStatusBarLabelList.at(1)->setText(temp);
3050 temp = i18n(" Column: %1 ", col + 1);
3051 mStatusBarLabelList.at(2)->setText(temp);
3053 // Show link target in status bar
3054 if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) {
3055 const QString text = mComposerBase->editor()->composerControler()->currentLinkText();
3056 const QString url = mComposerBase->editor()->composerControler()->currentLinkUrl();
3057 mStatusBarLabelList.at(0)->setText(text + QLatin1String(" -> ") + url);
3058 } else {
3059 mStatusBarLabelList.at(0)->clear();
3063 void KMComposerWin::recipientEditorSizeHintChanged()
3065 QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize);
3068 void KMComposerWin::setMaximumHeaderSize()
3070 mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height());
3073 void KMComposerWin::updateSignatureAndEncryptionStateIndicators()
3075 mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(mSignAction->isChecked(), mEncryptAction->isChecked());
3078 void KMComposerWin::slotDictionaryLanguageChanged(const QString &language)
3080 mComposerBase->dictionary()->setCurrentByDictionary(language);
3083 void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection)
3085 mComposerBase->setFcc(collection);
3086 mComposerBase->editor()->document()->setModified(true);
3089 void KMComposerWin::slotSaveAsFile()
3091 SaveAsFileJob *job = new SaveAsFileJob(this);
3092 job->setParentWidget(this);
3093 job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
3094 job->setTextDocument(mComposerBase->editor()->document());
3095 job->start();
3096 //not necessary to delete it. It done in SaveAsFileJob
3099 void KMComposerWin::slotAttachMissingFile()
3101 mComposerBase->attachmentController()->showAddAttachmentFileDialog();
3104 void KMComposerWin::slotVerifyMissingAttachmentTimeout()
3106 if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) {
3107 mAttachmentMissing->animatedShow();
3111 void KMComposerWin::slotExplicitClosedMissingAttachment()
3113 if (m_verifyMissingAttachment) {
3114 m_verifyMissingAttachment->stop();
3115 delete m_verifyMissingAttachment;
3116 m_verifyMissingAttachment = Q_NULLPTR;
3120 void KMComposerWin::addExtraCustomHeaders(const QMap<QByteArray, QString> &headers)
3122 mExtraHeaders = headers;
3125 void KMComposerWin::slotExternalEditorStarted()
3127 mComposerBase->identityCombo()->setEnabled(false);
3128 mExternalEditorWarning->show();
3131 void KMComposerWin::slotExternalEditorClosed()
3133 mComposerBase->identityCombo()->setEnabled(true);
3134 mExternalEditorWarning->hide();
3137 void KMComposerWin::slotInsertShortUrl(const QString &url)
3139 mComposerBase->editor()->composerControler()->insertLink(url);
3142 void KMComposerWin::slotShareLinkDone(const QString &link)
3144 mComposerBase->editor()->composerControler()->insertShareLink(link);
3147 void KMComposerWin::slotTransportChanged()
3149 mComposerBase->editor()->document()->setModified(true);
3152 void KMComposerWin::slotFollowUpMail(bool toggled)
3154 if (toggled) {
3155 QPointer<MessageComposer::FollowUpReminderSelectDateDialog> dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this);
3156 if (dlg->exec()) {
3157 mComposerBase->setFollowUpDate(dlg->selectedDate());
3158 mComposerBase->setFollowUpCollection(dlg->collection());
3159 } else {
3160 mFollowUpToggleAction->setChecked(false);
3162 delete dlg;
3163 } else {
3164 mComposerBase->clearFollowUp();
3168 void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b)
3170 mSnippetWidget->setVisible(b);
3171 mSnippetSplitterCollapser->setVisible(b);
3174 void KMComposerWin::slotOverwriteModeWasChanged(bool state)
3176 mComposerBase->editor()->setCursorWidth(state ? 5 : 1);
3177 mComposerBase->editor()->setOverwriteMode(state);
3180 QList<KToggleAction *> KMComposerWin::customToolsList() const
3182 return mCustomToolsWidget->actionList();
3185 QList<QAction *> KMComposerWin::pluginToolsActionListForPopupMenu() const
3187 return mPluginEditorManagerInterface->actionsType(MessageComposer::ActionType::PopupMenu);
3190 void KMComposerWin::slotInsertNonBreakingSpace()
3192 mComposerBase->editor()->insertPlainText(QChar(0x000A0));