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"
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>
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>
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"
88 #include <Libkleo/ProgressDialog>
89 #include <Libkleo/KeySelectionDialog>
90 #include <Libkleo/CryptoBackendFactory>
91 #include <Libkleo/ExportJob>
92 #include <Libkleo/SpecialJob>
95 #include <Libkdepim/KCursorSaver>
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>
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>
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>
157 #include <KIO/JobUiDelegate>
158 #include <QFileDialog>
159 #include <KEmailAddress>
160 #include <KEncodingFileDialog>
161 #include <KHelpClient>
163 #include <KConfigGroup>
164 #include <KXMLGUIFactory>
168 #include <qinputdialog.h>
169 #include <qstatusbar.h>
170 #include <QTemporaryDir>
172 #include <QClipboard>
175 #include <QTextDocumentWriter>
176 #include <QApplication>
178 #include <QStandardPaths>
179 #include <QFontDatabase>
180 #include <QMimeDatabase>
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#"),
216 mTextSelection(textSelection
),
217 mCustomTemplate(customTemplate
),
218 mSigningAndEncryptionExplicitlyDisabled(false),
219 mFolder(Akonadi::Collection(-1)),
220 mForceDisableHtml(false),
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
),
238 mComposerBase(Q_NULLPTR
),
239 m_verifyMissingAttachment(Q_NULLPTR
),
240 mPreventFccOverwrite(false),
241 mCheckForForgottenAttachments(true),
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
;
286 mHeadersToEditorSplitter
->setSizes(defaultSizes
);
288 QVBoxLayout
*v
= new QVBoxLayout(mMainWidget
);
290 v
->addWidget(mHeadersToEditorSplitter
);
291 KIdentityManagement::IdentityCombo
*identity
= new KIdentityManagement::IdentityCombo(kmkernel
->identityManager(),
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();
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
);
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());
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()) {
464 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm
);
467 if (KMailSettings::self()->useExternalEditor()) {
468 editor
->setUseExternalEditor(true);
469 editor
->setExternalEditorPath(KMailSettings::self()->externalEditor());
473 setMessage(aMsg
, lastSignState
, lastEncryptState
);
476 mComposerBase
->recipientsEditor()->setFocus();
477 editor
->composerActions()->updateActionStates(); // set toolbar buttons to correct values
481 mDummyComposer
= new MessageComposer::Composer(this);
482 mDummyComposer
->globalPart()->setParentWidgetForGui(this);
485 KMComposerWin::~KMComposerWin()
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
)
531 // TODO: find out, what the default send method is and send it this way
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);
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
)
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
);
573 mBodyFont
= KMailSettings::self()->composerFont();
574 mFixedFont
= MessageViewer::MessageViewerSettings::self()->fixedFont();
578 mEdtFrom
->setFont(mBodyFont
);
579 mEdtReplyTo
->setFont(mBodyFont
);
580 mEdtSubject
->setFont(mBodyFont
);
583 QSize siz
= KMailSettings::self()->composerSize();
584 if (siz
.width() < 200) {
587 if (siz
.height() < 200) {
592 if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) {
593 mSnippetSplitter
->setSizes(KMailSettings::self()->snippetSplitterPosition());
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());
611 if (!ident
.fcc().isEmpty()) {
612 fccName
= ident
.fcc();
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)
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
669 KToggleAction
*act
= ::qobject_cast
<KToggleAction
*>(sender());
675 if (act
== mAllFieldsAction
) {
677 } else if (act
== mIdentityAction
) {
679 } else if (act
== mTransportAction
) {
681 } else if (act
== mFromAction
) {
683 } else if (act
== mReplyToAction
) {
685 } else if (act
== mSubjectAction
) {
687 } else if (act
== mFccAction
) {
689 } else if (act
== mDictionaryAction
) {
692 qCDebug(KMAIL_LOG
) << "Something is wrong (Oh, yeah?)";
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()) {
702 mShowHeaders
= mShowHeaders
& ~id
;
704 mShowHeaders
= std::abs(mShowHeaders
);
711 mShowHeaders
= -std::abs(mShowHeaders
);
717 int KMComposerWin::calcColumnWidth(int which
, long allShowing
, int width
) const
719 if ((allShowing
& which
) == 0) {
724 if (which
== HDR_IDENTITY
) {
726 } else if (which
== HDR_DICTIONARY
) {
727 w
= mDictionaryLabel
;
728 } else if (which
== HDR_FCC
) {
730 } else if (which
== HDR_TRANSPORT
) {
732 } else if (which
== HDR_FROM
) {
734 } else if (which
== HDR_REPLY_TO
) {
736 } else if (which
== HDR_SUBJECT
) {
742 w
->setBuddy(mComposerBase
->editor()); // set dummy so we don't calculate width of '&' for this label.
745 return qMax(width
, w
->sizeHint().width());
748 void KMComposerWin::rethinkFields(bool fromSlot
)
750 //This sucks even more but again no ids. sorry (sven)
754 if (mShowHeaders
< 0) {
755 showHeaders
= HDR_ALL
;
757 showHeaders
= mShowHeaders
;
760 for (mask
= 1, mNumHeaders
= 0; mask
<= showHeaders
; mask
<<= 1) {
761 if ((showHeaders
& mask
) != 0) {
767 mGrid
= new QGridLayout(mHeadersArea
);
768 mGrid
->setColumnStretch(0, 1);
769 mGrid
->setColumnStretch(1, 100);
770 mGrid
->setRowStretch(mNumHeaders
+ 1, 100);
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
);
798 mAllFieldsAction
->setChecked(showHeaders
== HDR_ALL
);
802 mIdentityAction
->setChecked(std::abs(mShowHeaders
)&HDR_IDENTITY
);
804 rethinkHeaderLine(showHeaders
, HDR_IDENTITY
, row
, mLblIdentity
, mComposerBase
->identityCombo());
807 mDictionaryAction
->setChecked(std::abs(mShowHeaders
)&HDR_DICTIONARY
);
809 rethinkHeaderLine(showHeaders
, HDR_DICTIONARY
, row
, mDictionaryLabel
,
810 mComposerBase
->dictionary());
813 mFccAction
->setChecked(std::abs(mShowHeaders
)&HDR_FCC
);
815 rethinkHeaderLine(showHeaders
, HDR_FCC
, row
, mLblFcc
, mFccFolder
);
818 mTransportAction
->setChecked(std::abs(mShowHeaders
)&HDR_TRANSPORT
);
820 rethinkHeaderLine(showHeaders
, HDR_TRANSPORT
, row
, mLblTransport
, mComposerBase
->transportComboBox());
823 mFromAction
->setChecked(std::abs(mShowHeaders
)&HDR_FROM
);
825 rethinkHeaderLine(showHeaders
, HDR_FROM
, row
, mLblFrom
, mEdtFrom
);
827 QWidget
*prevFocus
= mEdtFrom
;
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);
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()));
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();
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()));
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);
899 void KMComposerWin::applyTemplate(uint uoid
, uint uOldId
)
901 const KIdentityManagement::Identity
&ident
= kmkernel
->identityManager()->identityForUoid(uoid
);
902 if (ident
.isNull()) {
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
;
912 mode
= TemplateParser::TemplateParser::NewMessage
;
915 mode
= TemplateParser::TemplateParser::Reply
;
918 mode
= TemplateParser::TemplateParser::ReplyAll
;
921 mode
= TemplateParser::TemplateParser::Forward
;
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
);
935 parser
.processWithIdentity(uoid
, mMsg
, mCollectionForNewMessage
);
937 mComposerBase
->updateTemplate(mMsg
);
938 updateSignature(uoid
, uOldId
);
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
));
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
);
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);
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"),
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
);
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()
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);
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());
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
1418 return MessageComposer::Util::cleanedUpHeaderString(mEdtReplyTo
->text());
1424 void KMComposerWin::setCurrentTransport(int transportId
)
1426 mComposerBase
->transportComboBox()->setCurrentTransport(transportId
);
1429 void KMComposerWin::setCurrentReplyTo(const QString
&replyTo
)
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
)
1445 qCDebug(KMAIL_LOG
) << "newMsg == 0!";
1449 if (lastSignState
) {
1450 mLastSignActionState
= true;
1453 if (lastEncryptState
) {
1454 mLastEncryptActionState
= true;
1457 mComposerBase
->setMessage(newMsg
, allowDecryption
);
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");
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();
1552 if (auto hdr
= mMsg
->headerByType("X-KMail-Fcc")) {
1553 kmailFcc
= hdr
->asUnicodeString();
1555 if (kmailFcc
.isEmpty()) {
1556 setFcc(ident
.fcc());
1560 if (auto hdr
= mMsg
->headerByType("X-KMail-Dictionary")) {
1561 const QString dictionary
= hdr
->asUnicodeString();
1562 if (!dictionary
.isEmpty()) {
1563 mComposerBase
->dictionary()->setCurrentByDictionary(dictionary
);
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();
1591 if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign
) {
1594 // Delay the signature appending. It may start a fileseletor.
1595 // Not user friendy if this modal fileseletor opens before the
1598 if (MessageComposer::MessageComposerSettings::self()->prependSignature()) {
1599 QTimer::singleShot(0, mComposerBase
->signatureController(), &MessageComposer::SignatureController::prependSignature
);
1601 QTimer::singleShot(0, mComposerBase
->signatureController(), &MessageComposer::SignatureController::appendSignature
);
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
)
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();
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
);
1667 mEdtFrom
->setModified(false);
1669 mEdtReplyTo
->setModified(false);
1671 mComposerBase
->recipientsEditor()->clearModified();
1672 mEdtSubject
->document()->setModified(false);
1676 bool KMComposerWin::queryClose()
1678 if (!mComposerBase
->editor()->checkExternalEditorFinished()) {
1681 if (kmkernel
->shuttingDown() || qApp
->isSavingSession()) {
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
) {
1704 } else if (rc
== KMessageBox::Yes
) {
1705 // doSend will close the window. Just return false from this method
1713 //else fall through: return true
1715 mComposerBase
->cleanupAutoSave();
1717 if (!mMiscComposers
.isEmpty()) {
1718 qCWarning(KMAIL_LOG
) << "Tried to close while composer was active";
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();
1750 mWasModified
= true;
1751 changeModifiedState(false);
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
)
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()
1775 mComposerBase
->cleanupAutoSave();
1776 mFolder
= Akonadi::Collection(); // see dtor
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
);
1799 void KMComposerWin::slotAddressBook()
1801 KRun::runCommand(QStringLiteral("kaddressbook"), window());
1804 void KMComposerWin::slotInsertFile()
1806 const QUrl u
= insertFile();
1811 mRecentAction
->addUrl(u
);
1812 // Prevent race condition updating list when multiple composers are open
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
) {
1824 while (encodings
.count() > mMaxRecentFiles
) {
1825 encodings
.removeLast();
1828 if (urls
.count() != encodings
.count()) {
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()) {
1856 // Get the encoding previously used when inserting this file
1858 const QStringList urls
= KMailSettings::self()->recentUrls();
1859 const QStringList encodings
= KMailSettings::self()->recentEncodings();
1860 const int index
= urls
.indexOf(u
.toDisplayString());
1862 encoding
= encodings
[ index
];
1864 qCDebug(KMAIL_LOG
) << " encoding not found so we can't insert text"; //see InsertTextFileJob
1868 MessageComposer::InsertTextFileJob
*job
= new MessageComposer::InsertTextFileJob(mComposerBase
->editor(), u
);
1869 job
->setEncoding(encoding
);
1870 connect(job
, &KJob::result
, this, &KMComposerWin::slotInsertTextFile
);
1874 bool KMComposerWin::showErrorMessage(KJob
*job
)
1877 if (static_cast<KIO::Job
*>(job
)->ui()) {
1878 static_cast<KIO::Job
*>(job
)->ui()->showErrorMessage();
1880 qCDebug(KMAIL_LOG
) << " job->errorString() :" << job
->errorString();
1887 void KMComposerWin::slotInsertTextFile(KJob
*job
)
1889 showErrorMessage(job
);
1892 void KMComposerWin::slotSelectCryptoModule(bool init
)
1898 mComposerBase
->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments());
1899 mComposerBase
->attachmentModel()->setSignEnabled(canSignEncryptAttachments());
1902 void KMComposerWin::slotUpdateFont()
1904 if (!mFixedFontAction
) {
1907 mComposerBase
->editor()->composerControler()->setFontForWholeText(mFixedFontAction
->isChecked() ?
1908 mFixedFont
: mBodyFont
);
1911 QUrl
KMComposerWin::insertFile()
1913 const KEncodingFileDialog::Result result
= KEncodingFileDialog::getOpenUrlAndEncoding(QString(),
1917 i18nc("@title:window", "Insert File"));
1919 if (!result
.URLs
.isEmpty()) {
1920 url
= result
.URLs
.first();
1921 if (url
.isValid()) {
1922 MessageCore::StringUtil::setEncodingFile(url
, MimeTreeParser::NodeHelper::fixEncoding(result
.encoding
));
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
) {
1938 if (url
.scheme() == QLatin1String("mailto")) {
1939 urlStr
= KEmailAddress::decodeMailtoUrl(url
);
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()) {
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());
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
);
1978 } else if (!selectedAction
) {
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.
1987 const QString attName
=
1988 QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal
, QString(), &ok
);
1992 addAttachment(attName
, KMime::Headers::CEbase64
, QString(), imageData
, "image/png");
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()) {
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());
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());
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";
2061 void KMComposerWin::slotPasteAsAttachment()
2063 const QMimeData
*mimeData
= QApplication::clipboard()->mimeData();
2064 if (insertFromMimeData(mimeData
, true)) {
2067 if (mimeData
->hasText()) {
2069 const QString attName
= QInputDialog::getText(this,
2070 i18n("Insert clipboard text as attachment"),
2071 i18n("Name of the attachment:"), QLineEdit::Normal
,
2074 mComposerBase
->addAttachment(attName
, attName
, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
2080 void KMComposerWin::slotFetchJob(KJob
*job
)
2082 if (showErrorMessage(job
)) {
2085 Akonadi::ItemFetchJob
*fjob
= qobject_cast
<Akonadi::ItemFetchJob
*>(job
);
2089 const Akonadi::Item::List items
= fjob
->items();
2091 if (items
.isEmpty()) {
2095 if (items
.first().mimeType() == KMime::Message::mimeType()) {
2097 if (items
.at(0).isValid() && items
.at(0).parentCollection().isValid()) {
2098 QSharedPointer
<MailCommon::FolderCollection
> fd(MailCommon::FolderCollection::forCollection(items
.at(0).parentCollection(), false));
2100 identity
= fd
->identity();
2103 KMCommand
*command
= new KMForwardAttachedCommand(this, items
, identity
, this);
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
);
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()
2147 void KMComposerWin::slotNewComposer()
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();
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.
2171 setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')'));
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();
2189 if (!mEncryptAction
->isEnabled()) {
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
) {
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>"
2204 i18n("Undefined Encryption Key"));
2205 setModified(wasModified
);
2210 // make sure the mEncryptAction is in the right state
2211 mEncryptAction
->setChecked(encrypt
);
2213 updateSignatureAndEncryptionStateIndicators();
2215 // show the appropriate icon
2217 mEncryptAction
->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt")));
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();
2240 if (!mSignAction
->isEnabled()) {
2244 // check if the user defined a signing key for the current identity
2245 if (sign
&& !mLastIdentityHasSigningKey
) {
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 "
2252 "<p>Please select the key to use "
2253 "in the identity configuration.</p>"
2255 i18n("Undefined Signing Key"));
2256 setModified(wasModified
);
2261 // make sure the mSignAction is in the right state
2262 mSignAction
->setChecked(sign
);
2265 updateSignatureAndEncryptionStateIndicators();
2267 // mark the attachments for (no) signing
2268 if (canSignEncryptAttachments()) {
2269 mComposerBase
->attachmentModel()->setSignSelected(sign
);
2273 void KMComposerWin::slotWordWrapToggled(bool on
)
2276 mComposerBase
->editor()->enableWordWrap(validateLineWrapWidth());
2282 int KMComposerWin::validateLineWrapWidth()
2284 int lineWrap
= MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
2285 if ((lineWrap
== 0) || (lineWrap
> 78)) {
2287 } else if (lineWrap
< 30) {
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
);
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
,
2358 command
->setPrintPreview(preview
);
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."));
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."));
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
) {
2411 if (subject().isEmpty()) {
2412 mEdtSubject
->setFocus();
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
) {
2426 const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment
= userForgotAttachment();
2427 if ((forgotAttachment
== MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment
) ||
2428 (forgotAttachment
== MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel
)) {
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
)) {
2450 const QStringList recipients
= { mComposerBase
->to().trimmed(), mComposerBase
->cc().trimmed(), mComposerBase
->bcc().trimmed()};
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
);
2462 // we'll call send from within slotDoDelaySend
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.")
2473 doDelayedSend(method
, saveIn
);
2477 void KMComposerWin::slotDoDelayedSend(KJob
*job
)
2480 KMessageBox::error(this, job
->errorText());
2485 const AddressValidationJob
*validateJob
= qobject_cast
<AddressValidationJob
*>(job
);
2487 // Abort sending if one of the recipient addresses is invalid ...
2488 if (!validateJob
->isValid()) {
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());
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
)) {
2554 if (!checkRecipientNumber()) {
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);
2564 const SendLater::SendLaterDialog::SendLaterAction action
= dlg
->action();
2567 case SendLater::SendLaterDialog::Unknown
:
2568 qCDebug(KMAIL_LOG
) << "Sendlater action \"Unknown\": Need to fix it.";
2570 case SendLater::SendLaterDialog::Canceled
:
2573 case SendLater::SendLaterDialog::PutInOutbox
:
2574 doSend(MessageComposer::MessageSender::SendLater
);
2576 case SendLater::SendLaterDialog::SendDeliveryAtTime
: {
2577 mComposerBase
->setSendLaterInfo(info
);
2578 if (info
->isRecurrence()) {
2579 doSend(MessageComposer::MessageSender::SendLater
, MessageComposer::MessageSender::SaveInTemplates
);
2581 doSend(MessageComposer::MessageSender::SendLater
, MessageComposer::MessageSender::SaveInDrafts
);
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
)
2612 mComposerBase
->transportComboBox()->setCurrentTransport(transport
->id());
2617 void KMComposerWin::slotSendLaterVia(MailTransport::Transport
*transport
)
2620 mComposerBase
->transportComboBox()->setCurrentTransport(transport
->id());
2625 void KMComposerWin::sendNow(bool shortcutUsed
)
2627 if (!mComposerBase
->editor()->checkExternalEditorFinished()) {
2630 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists
)) {
2633 if (!checkRecipientNumber()) {
2636 mSendNowByShortcutUsed
= shortcutUsed
;
2637 if (KMailSettings::self()->checkSpellingBeforeSend()) {
2638 mComposerBase
->editor()->forceSpellChecking();
2644 void KMComposerWin::slotSendNowByShortcut()
2649 void KMComposerWin::slotSendNow()
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();
2674 if (mSendNowByShortcutUsed
) {
2675 if (!KMailSettings::self()->checkSendDefaultActionShortcut()) {
2676 ValidateSendMailShortcut
validateShortcut(actionCollection(), this);
2677 if (!validateShortcut
.validate()) {
2681 if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) {
2682 confirmBeforeSend();
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
);
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
);
2709 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList
&list
)
2711 if (list
.isEmpty()) {
2712 slotCheckSendNowStep2();
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
) {
2733 void KMComposerWin::enableHtml()
2735 if (mForceDisableHtml
) {
2736 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded
);
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"));
2766 case KMessageBox::Cancel
:
2769 case KMessageBox::No
:
2770 forcePlainTextMarkup
= true;
2772 case KMessageBox::Yes
:
2777 mComposerBase
->editor()->forcePlainTextMarkup(forcePlainTextMarkup
);
2778 mComposerBase
->editor()->switchToPlainText();
2779 mComposerBase
->editor()->composerActions()->setActionsEnabled(false);
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?
2805 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible
)
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!";
2844 const KIdentityManagement::Identity
&ident
=
2845 KMKernel::self()->identityManager()->identityForUoid(uoid
);
2846 if (ident
.isNull()) {
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
;
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
>();
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");
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);
2891 mMsg
->removeHeader("X-KMail-Transport");
2892 mComposerBase
->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
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
);
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()) &&
2918 applyTemplate(uoid
, mId
);
2920 mComposerBase
->identityChanged(ident
, oldIdentity
, false);
2921 mEdtSubject
->setAutocorrectionLanguage(ident
.autocorrectionLanguage());
2924 // disable certain actions if there is no PGP user identity set
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();
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
);
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
);
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
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();
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
);
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());
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
)
3155 QPointer
<MessageComposer::FollowUpReminderSelectDateDialog
> dlg
= new MessageComposer::FollowUpReminderSelectDateDialog(this);
3157 mComposerBase
->setFollowUpDate(dlg
->selectedDate());
3158 mComposerBase
->setFollowUpCollection(dlg
->collection());
3160 mFollowUpToggleAction
->setChecked(false);
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));