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
, &KPIMTextEdit::RichTextEditorWidget::slotFind
, actionCollection());
1141 mFindNextText
= KStandardAction::findNext(mRichTextEditorwidget
, &KPIMTextEdit::RichTextEditorWidget::slotFindNext
, actionCollection());
1143 mReplaceText
= KStandardAction::replace(mRichTextEditorwidget
, &KPIMTextEdit::RichTextEditorWidget::slotReplace
, actionCollection());
1144 actionCollection()->addAction(KStandardAction::Spelling
, QStringLiteral("spellcheck"),
1145 mComposerBase
->editor(), SLOT(slotCheckSpelling()));
1147 action
= new QAction(i18n("Paste as Attac&hment"), this);
1148 actionCollection()->addAction(QStringLiteral("paste_att"), action
);
1149 connect(action
, &QAction::triggered
, this, &KMComposerWin::slotPasteAsAttachment
);
1151 action
= new QAction(i18n("Cl&ean Spaces"), this);
1152 actionCollection()->addAction(QStringLiteral("clean_spaces"), action
);
1153 connect(action
, &QAction::triggered
, mComposerBase
->signatureController(), &MessageComposer::SignatureController::cleanSpace
);
1155 mFixedFontAction
= new KToggleAction(i18n("Use Fi&xed Font"), this);
1156 actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction
);
1157 connect(mFixedFontAction
, &KToggleAction::triggered
, this, &KMComposerWin::slotUpdateFont
);
1158 mFixedFontAction
->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont());
1160 //these are checkable!!!
1161 mUrgentAction
= new KToggleAction(
1162 i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this);
1163 actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction
);
1164 mRequestMDNAction
= new KToggleAction(i18n("&Request Disposition Notification"), this);
1165 actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction
);
1166 mRequestMDNAction
->setChecked(KMailSettings::self()->requestMDN());
1167 //----- Message-Encoding Submenu
1168 mCodecAction
= new CodecAction(CodecAction::ComposerMode
, this);
1169 actionCollection()->addAction(QStringLiteral("charsets"), mCodecAction
);
1170 mWordWrapAction
= new KToggleAction(i18n("&Wordwrap"), this);
1171 actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction
);
1172 mWordWrapAction
->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap());
1173 connect(mWordWrapAction
, &KToggleAction::toggled
, this, &KMComposerWin::slotWordWrapToggled
);
1175 mSnippetAction
= new KToggleAction(i18n("&Snippets"), this);
1176 actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction
);
1177 connect(mSnippetAction
, &KToggleAction::toggled
, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged
);
1178 mSnippetAction
->setChecked(KMailSettings::self()->showSnippetManager());
1180 mAutoSpellCheckingAction
= new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")),
1181 i18n("&Automatic Spellchecking"),
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
);
1304 createGUI(QStringLiteral("kmcomposerui.rc"));
1305 initializePluginActions();
1306 connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled
,
1307 this, &KMComposerWin::htmlToolBarVisibilityChanged
);
1309 // In Kontact, this entry would read "Configure Kontact", but bring
1310 // up KMail's config dialog. That's sensible, though, so fix the label.
1311 QAction
*configureAction
= actionCollection()->action(QStringLiteral("options_configure"));
1312 if (configureAction
) {
1313 configureAction
->setText(i18n("Configure KMail..."));
1317 void KMComposerWin::initializePluginActions()
1320 QHashIterator
<MessageComposer::ActionType::Type
, QList
<QAction
*> > localActionsType(mPluginEditorManagerInterface
->actionsType());
1321 while (localActionsType
.hasNext()) {
1322 localActionsType
.next();
1323 QList
<QAction
*> lst
= localActionsType
.value();
1324 if (!lst
.isEmpty()) {
1325 const QString actionlistname
= QStringLiteral("kmaileditor") + MessageComposer::PluginEditorInterface::actionXmlExtension(localActionsType
.key());
1326 Q_FOREACH (KXMLGUIClient
*client
, guiFactory()->clients()) {
1327 client
->unplugActionList(actionlistname
);
1328 client
->plugActionList(actionlistname
, lst
);
1335 void KMComposerWin::changeCryptoAction()
1337 const KIdentityManagement::Identity
&ident
=
1338 KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase
->identityCombo()->currentIdentity());
1340 if (!Kleo::CryptoBackendFactory::instance()->openpgp() && !Kleo::CryptoBackendFactory::instance()->smime()) {
1341 // no crypto whatsoever
1342 mEncryptAction
->setEnabled(false);
1343 setEncryption(false);
1344 mSignAction
->setEnabled(false);
1347 const bool canOpenPGPSign
= Kleo::CryptoBackendFactory::instance()->openpgp() &&
1348 !ident
.pgpSigningKey().isEmpty();
1349 const bool canSMIMESign
= Kleo::CryptoBackendFactory::instance()->smime() &&
1350 !ident
.smimeSigningKey().isEmpty();
1352 setEncryption(false);
1353 setSigning((canOpenPGPSign
|| canSMIMESign
) && ident
.pgpAutoSign());
1358 void KMComposerWin::setupStatusBar(QWidget
*w
)
1360 //KPIM::ProgressStatusBarWidget *progressStatusBarWidget = new KPIM::ProgressStatusBarWidget(statusBar(), this, PimCommon::StorageServiceProgressManager::progressTypeValue());
1361 statusBar()->addWidget(w
);
1362 QLabel
*lab
= new QLabel(this);
1363 lab
->setAlignment(Qt::AlignLeft
| Qt::AlignVCenter
);
1364 statusBar()->addPermanentWidget(lab
);
1365 mStatusBarLabelList
.append(lab
);
1367 lab
= new QLabel(this);
1368 lab
->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 "
1369 , QStringLiteral(" ")));
1370 statusBar()->addPermanentWidget(lab
);
1371 mStatusBarLabelList
.append(lab
);
1373 lab
= new QLabel(i18n(" Column: %1 ", QStringLiteral(" ")));
1374 statusBar()->addPermanentWidget(lab
);
1375 mStatusBarLabelList
.append(lab
);
1377 mStatusBarLabelToggledOverrideMode
= new StatusBarLabelToggledState(this);
1378 mStatusBarLabelToggledOverrideMode
->setStateString(i18n("OVR"), i18n("INS"));
1379 statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode
, 0);
1380 connect(mStatusBarLabelToggledOverrideMode
, &StatusBarLabelToggledState::toggleModeChanged
, this, &KMComposerWin::slotOverwriteModeWasChanged
);
1382 mStatusBarLabelSpellCheckingChangeMode
= new StatusBarLabelToggledState(this);
1383 mStatusBarLabelSpellCheckingChangeMode
->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off"));
1384 statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode
, 0);
1385 connect(mStatusBarLabelSpellCheckingChangeMode
, &StatusBarLabelToggledState::toggleModeChanged
, this, &KMComposerWin::slotAutoSpellCheckingToggled
);
1387 //statusBar()->addPermanentWidget(progressStatusBarWidget->littleProgress());
1391 void KMComposerWin::setupEditor(void)
1393 QFontMetrics
fm(mBodyFont
);
1394 mComposerBase
->editor()->setTabStopWidth(fm
.width(QLatin1Char(' ')) * 8);
1396 slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap());
1401 connect(mComposerBase
->editor(), &QTextEdit::cursorPositionChanged
,
1402 this, &KMComposerWin::slotCursorPositionChanged
);
1403 slotCursorPositionChanged();
1406 QString
KMComposerWin::subject() const
1408 return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject
->toPlainText());
1411 QString
KMComposerWin::from() const
1413 return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom
->text());
1416 QString
KMComposerWin::replyTo() const
1419 return MessageComposer::Util::cleanedUpHeaderString(mEdtReplyTo
->text());
1425 void KMComposerWin::setCurrentTransport(int transportId
)
1427 mComposerBase
->transportComboBox()->setCurrentTransport(transportId
);
1430 void KMComposerWin::setCurrentReplyTo(const QString
&replyTo
)
1433 mEdtReplyTo
->setText(replyTo
);
1437 uint
KMComposerWin::currentIdentity() const
1439 return mComposerBase
->identityCombo()->currentIdentity();
1442 void KMComposerWin::setMessage(const KMime::Message::Ptr
&newMsg
, bool lastSignState
, bool lastEncryptState
, bool mayAutoSign
,
1443 bool allowDecryption
, bool isModified
)
1446 qCDebug(KMAIL_LOG
) << "newMsg == 0!";
1450 if (lastSignState
) {
1451 mLastSignActionState
= true;
1454 if (lastEncryptState
) {
1455 mLastEncryptActionState
= true;
1458 mComposerBase
->setMessage(newMsg
, allowDecryption
);
1460 KIdentityManagement::IdentityManager
*im
= KMKernel::self()->identityManager();
1462 mEdtFrom
->setText(mMsg
->from()->asUnicodeString());
1463 mEdtSubject
->setText(mMsg
->subject()->asUnicodeString());
1465 // Restore the quote prefix. We can't just use the global quote prefix here,
1466 // since the prefix is different for each message, it might for example depend
1467 // on the original sender in a reply.
1468 if (auto hdr
= mMsg
->headerByType("X-KMail-QuotePrefix")) {
1469 mComposerBase
->editor()->setQuotePrefixName(hdr
->asUnicodeString());
1472 if (newMsg
->headerByType("X-KMail-Identity") &&
1473 !newMsg
->headerByType("X-KMail-Identity")->asUnicodeString().isEmpty()) {
1474 mId
= newMsg
->headerByType("X-KMail-Identity")->asUnicodeString().toUInt();
1477 // don't overwrite the header values with identity specific values
1478 disconnect(mComposerBase
->identityCombo(), SIGNAL(identityChanged(uint
)),
1479 this, SLOT(slotIdentityChanged(uint
)));
1481 // load the mId into the gui, sticky or not, without emitting
1482 mComposerBase
->identityCombo()->setCurrentIdentity(mId
);
1483 connect(mComposerBase
->identityCombo(), SIGNAL(identityChanged(uint
)),
1484 this, SLOT(slotIdentityChanged(uint
)));
1486 // manually load the identity's value into the fields; either the one from the
1487 // messge, where appropriate, or the one from the sticky identity. What's in
1488 // mId might have changed meanwhile, thus the save value
1489 slotIdentityChanged(mId
, true /*initalChange*/);
1490 // Fixing the identitis with auto signing activated
1491 mLastSignActionState
= mSignAction
->isChecked();
1493 const KIdentityManagement::Identity
&ident
= im
->identityForUoid(mComposerBase
->identityCombo()->currentIdentity());
1495 // check for the presence of a DNT header, indicating that MDN's were requested
1496 if (auto hdr
= newMsg
->headerByType("Disposition-Notification-To")) {
1497 QString mdnAddr
= hdr
->asUnicodeString();
1498 mRequestMDNAction
->setChecked((!mdnAddr
.isEmpty() &&
1499 im
->thatIsMe(mdnAddr
)) ||
1500 KMailSettings::self()->requestMDN());
1502 // check for presence of a priority header, indicating urgent mail:
1503 if (newMsg
->headerByType("X-PRIORITY") && newMsg
->headerByType("Priority")) {
1504 const QString xpriority
= newMsg
->headerByType("X-PRIORITY")->asUnicodeString();
1505 const QString priority
= newMsg
->headerByType("Priority")->asUnicodeString();
1506 if (xpriority
== QLatin1String("2 (High)") && priority
== QLatin1String("urgent")) {
1507 mUrgentAction
->setChecked(true);
1511 if (!ident
.isXFaceEnabled() || ident
.xface().isEmpty()) {
1512 mMsg
->removeHeader("X-Face");
1514 QString xface
= ident
.xface();
1515 if (!xface
.isEmpty()) {
1516 int numNL
= (xface
.length() - 1) / 70;
1517 for (int i
= numNL
; i
> 0; --i
) {
1518 xface
.insert(i
* 70, QStringLiteral("\n\t"));
1520 auto header
= new KMime::Headers::Generic("X-Face");
1521 header
->fromUnicodeString(xface
, "utf-8");
1522 mMsg
->setHeader(header
);
1526 // if these headers are present, the state of the message should be overruled
1527 if (auto hdr
= mMsg
->headerByType("X-KMail-SignatureActionEnabled")) {
1528 mLastSignActionState
= (hdr
->as7BitString(false).contains("true"));
1530 if (auto hdr
= mMsg
->headerByType("X-KMail-EncryptActionEnabled")) {
1531 mLastEncryptActionState
= (hdr
->as7BitString(false).contains("true"));
1533 if (auto hdr
= mMsg
->headerByType("X-KMail-CryptoMessageFormat")) {
1534 mCryptoModuleAction
->setCurrentItem(format2cb(static_cast<Kleo::CryptoMessageFormat
>(
1535 hdr
->asUnicodeString().toInt())));
1538 mLastIdentityHasSigningKey
= !ident
.pgpSigningKey().isEmpty() || !ident
.smimeSigningKey().isEmpty();
1539 mLastIdentityHasEncryptionKey
= !ident
.pgpEncryptionKey().isEmpty() || !ident
.smimeEncryptionKey().isEmpty();
1541 if (Kleo::CryptoBackendFactory::instance()->openpgp() || Kleo::CryptoBackendFactory::instance()->smime()) {
1542 const bool canOpenPGPSign
= Kleo::CryptoBackendFactory::instance()->openpgp() &&
1543 !ident
.pgpSigningKey().isEmpty();
1544 const bool canSMIMESign
= Kleo::CryptoBackendFactory::instance()->smime() &&
1545 !ident
.smimeSigningKey().isEmpty();
1547 setEncryption(mLastEncryptActionState
);
1548 setSigning((canOpenPGPSign
|| canSMIMESign
) && mLastSignActionState
);
1550 updateSignatureAndEncryptionStateIndicators();
1553 if (auto hdr
= mMsg
->headerByType("X-KMail-Fcc")) {
1554 kmailFcc
= hdr
->asUnicodeString();
1556 if (kmailFcc
.isEmpty()) {
1557 setFcc(ident
.fcc());
1561 if (auto hdr
= mMsg
->headerByType("X-KMail-Dictionary")) {
1562 const QString dictionary
= hdr
->asUnicodeString();
1563 if (!dictionary
.isEmpty()) {
1564 mComposerBase
->dictionary()->setCurrentByDictionary(dictionary
);
1567 mComposerBase
->dictionary()->setCurrentByDictionaryName(ident
.dictionary());
1570 mEdtReplyTo
->setText(mMsg
->replyTo()->asUnicodeString());
1572 KMime::Content
*msgContent
= new KMime::Content
;
1573 msgContent
->setContent(mMsg
->encodedContent());
1574 msgContent
->parse();
1575 MessageViewer::EmptySource emptySource
;
1576 MimeTreeParser::ObjectTreeParser
otp(&emptySource
); //All default are ok
1577 emptySource
.setAllowDecryption(allowDecryption
);
1578 otp
.parseObjectTree(msgContent
);
1580 bool shouldSetCharset
= false;
1581 if ((mContext
== Reply
|| mContext
== ReplyToAll
|| mContext
== Forward
) && MessageComposer::MessageComposerSettings::forceReplyCharset()) {
1582 shouldSetCharset
= true;
1584 if (shouldSetCharset
&& !otp
.plainTextContentCharset().isEmpty()) {
1585 mOriginalPreferredCharset
= otp
.plainTextContentCharset();
1587 // always set auto charset, but prefer original when composing if force reply is set.
1588 mCodecAction
->setAutoCharset();
1592 if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign
) {
1595 // Delay the signature appending. It may start a fileseletor.
1596 // Not user friendy if this modal fileseletor opens before the
1599 if (MessageComposer::MessageComposerSettings::self()->prependSignature()) {
1600 QTimer::singleShot(0, mComposerBase
->signatureController(), &MessageComposer::SignatureController::prependSignature
);
1602 QTimer::singleShot(0, mComposerBase
->signatureController(), &MessageComposer::SignatureController::appendSignature
);
1605 mComposerBase
->editor()->externalComposer()->startExternalEditor();
1608 setModified(isModified
);
1610 // honor "keep reply in this folder" setting even when the identity is changed later on
1611 mPreventFccOverwrite
= (!kmailFcc
.isEmpty() && ident
.fcc() != kmailFcc
);
1612 QTimer::singleShot(0, this, &KMComposerWin::forceAutoSaveMessage
); //Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in.
1615 void KMComposerWin::setAutoSaveFileName(const QString
&fileName
)
1617 mComposerBase
->setAutoSaveFileName(fileName
);
1620 void KMComposerWin::setSigningAndEncryptionDisabled(bool v
)
1622 mSigningAndEncryptionExplicitlyDisabled
= v
;
1625 void KMComposerWin::setFolder(const Akonadi::Collection
&aFolder
)
1630 void KMComposerWin::setFcc(const QString
&idString
)
1632 // check if the sent-mail folder still exists
1633 Akonadi::Collection col
;
1634 if (idString
.isEmpty()) {
1635 col
= CommonKernel
->sentCollectionFolder();
1637 col
= Akonadi::Collection(idString
.toLongLong());
1640 mComposerBase
->setFcc(col
);
1641 mFccFolder
->setCollection(col
);
1644 bool KMComposerWin::isComposerModified() const
1646 return (mComposerBase
->editor()->document()->isModified() ||
1647 mEdtFrom
->isModified() ||
1648 (mEdtReplyTo
&& mEdtReplyTo
->isModified()) ||
1649 mComposerBase
->recipientsEditor()->isModified() ||
1650 mEdtSubject
->document()->isModified());
1653 bool KMComposerWin::isModified() const
1655 return mWasModified
|| isComposerModified();
1658 void KMComposerWin::setModified(bool modified
)
1660 mWasModified
= modified
;
1661 changeModifiedState(modified
);
1664 void KMComposerWin::changeModifiedState(bool modified
)
1666 mComposerBase
->editor()->document()->setModified(modified
);
1668 mEdtFrom
->setModified(false);
1670 mEdtReplyTo
->setModified(false);
1672 mComposerBase
->recipientsEditor()->clearModified();
1673 mEdtSubject
->document()->setModified(false);
1677 bool KMComposerWin::queryClose()
1679 if (!mComposerBase
->editor()->checkExternalEditorFinished()) {
1682 if (kmkernel
->shuttingDown() || qApp
->isSavingSession()) {
1687 const bool istemplate
= (mFolder
.isValid() && CommonKernel
->folderIsTemplates(mFolder
));
1688 const QString savebut
= (istemplate
?
1689 i18n("Re&save as Template") :
1690 i18n("&Save as Draft"));
1691 const QString savetext
= (istemplate
?
1692 i18n("Resave this message in the Templates folder. "
1693 "It can then be used at a later time.") :
1694 i18n("Save this message in the Drafts folder. "
1695 "It can then be edited and sent at a later time."));
1697 const int rc
= KMessageBox::warningYesNoCancel(this,
1698 i18n("Do you want to save the message for later or discard it?"),
1699 i18n("Close Composer"),
1700 KGuiItem(savebut
, QStringLiteral("document-save"), QString(), savetext
),
1701 KStandardGuiItem::discard(),
1702 KStandardGuiItem::cancel());
1703 if (rc
== KMessageBox::Cancel
) {
1705 } else if (rc
== KMessageBox::Yes
) {
1706 // doSend will close the window. Just return false from this method
1714 //else fall through: return true
1716 mComposerBase
->cleanupAutoSave();
1718 if (!mMiscComposers
.isEmpty()) {
1719 qCWarning(KMAIL_LOG
) << "Tried to close while composer was active";
1725 MessageComposer::ComposerViewBase::MissingAttachment
KMComposerWin::userForgotAttachment()
1727 bool checkForForgottenAttachments
= mCheckForForgottenAttachments
&& KMailSettings::self()->showForgottenAttachmentWarning();
1729 if (!checkForForgottenAttachments
) {
1730 return MessageComposer::ComposerViewBase::NoMissingAttachmentFound
;
1733 mComposerBase
->setSubject(subject()); //be sure the composer knows the subject
1734 MessageComposer::ComposerViewBase::MissingAttachment missingAttachments
= mComposerBase
->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords());
1736 return missingAttachments
;
1739 void KMComposerWin::forceAutoSaveMessage()
1741 autoSaveMessage(true);
1744 void KMComposerWin::autoSaveMessage(bool force
)
1746 if (isComposerModified() || force
) {
1747 applyComposerSetting(mComposerBase
);
1748 mComposerBase
->saveMailSettings();
1749 mComposerBase
->autoSaveMessage();
1751 mWasModified
= true;
1752 changeModifiedState(false);
1755 mComposerBase
->updateAutoSave();
1759 bool KMComposerWin::encryptToSelf() const
1761 return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf();
1764 void KMComposerWin::slotSendFailed(const QString
&msg
, MessageComposer::ComposerViewBase::FailedType type
)
1767 if (!msg
.isEmpty()) {
1768 KMessageBox::sorry(mMainWidget
, msg
,
1769 (type
== MessageComposer::ComposerViewBase::AutoSave
) ? i18n("Autosave Message Failed") : i18n("Sending Message Failed"));
1773 void KMComposerWin::slotSendSuccessful()
1776 mComposerBase
->cleanupAutoSave();
1777 mFolder
= Akonadi::Collection(); // see dtor
1781 const KIdentityManagement::Identity
&KMComposerWin::identity() const
1783 return KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase
->identityCombo()->currentIdentity());
1786 Kleo::CryptoMessageFormat
KMComposerWin::cryptoMessageFormat() const
1788 if (!mCryptoModuleAction
) {
1789 return Kleo::AutoFormat
;
1791 return cb2format(mCryptoModuleAction
->currentItem());
1794 void KMComposerWin::addAttach(KMime::Content
*msgPart
)
1796 mComposerBase
->addAttachmentPart(msgPart
);
1800 void KMComposerWin::slotAddressBook()
1802 KRun::runCommand(QStringLiteral("kaddressbook"), window());
1805 void KMComposerWin::slotInsertFile()
1807 const QUrl u
= insertFile();
1812 mRecentAction
->addUrl(u
);
1813 // Prevent race condition updating list when multiple composers are open
1816 const QString encoding
= MimeTreeParser::NodeHelper::encodingForName(query
.queryItemValue(QStringLiteral("charset")));
1817 QStringList urls
= KMailSettings::self()->recentUrls();
1818 QStringList encodings
= KMailSettings::self()->recentEncodings();
1819 // Prevent config file from growing without bound
1820 // Would be nicer to get this constant from KRecentFilesAction
1821 const int mMaxRecentFiles
= 30;
1822 while (urls
.count() > mMaxRecentFiles
) {
1825 while (encodings
.count() > mMaxRecentFiles
) {
1826 encodings
.removeLast();
1829 if (urls
.count() != encodings
.count()) {
1833 urls
.prepend(u
.toDisplayString());
1834 encodings
.prepend(encoding
);
1835 KMailSettings::self()->setRecentUrls(urls
);
1836 KMailSettings::self()->setRecentEncodings(encodings
);
1837 mRecentAction
->saveEntries(KMKernel::self()->config()->group(QString()));
1839 slotInsertRecentFile(u
);
1842 void KMComposerWin::slotRecentListFileClear()
1844 KSharedConfig::Ptr config
= KMKernel::self()->config();
1845 KConfigGroup
group(config
, "Composer");
1846 group
.deleteEntry("recent-urls");
1847 group
.deleteEntry("recent-encodings");
1848 mRecentAction
->saveEntries(config
->group(QString()));
1851 void KMComposerWin::slotInsertRecentFile(const QUrl
&u
)
1853 if (u
.fileName().isEmpty()) {
1857 // Get the encoding previously used when inserting this file
1859 const QStringList urls
= KMailSettings::self()->recentUrls();
1860 const QStringList encodings
= KMailSettings::self()->recentEncodings();
1861 const int index
= urls
.indexOf(u
.toDisplayString());
1863 encoding
= encodings
[ index
];
1865 qCDebug(KMAIL_LOG
) << " encoding not found so we can't insert text"; //see InsertTextFileJob
1869 MessageComposer::InsertTextFileJob
*job
= new MessageComposer::InsertTextFileJob(mComposerBase
->editor(), u
);
1870 job
->setEncoding(encoding
);
1871 connect(job
, &KJob::result
, this, &KMComposerWin::slotInsertTextFile
);
1875 bool KMComposerWin::showErrorMessage(KJob
*job
)
1878 if (static_cast<KIO::Job
*>(job
)->ui()) {
1879 static_cast<KIO::Job
*>(job
)->ui()->showErrorMessage();
1881 qCDebug(KMAIL_LOG
) << " job->errorString() :" << job
->errorString();
1888 void KMComposerWin::slotInsertTextFile(KJob
*job
)
1890 showErrorMessage(job
);
1893 void KMComposerWin::slotSelectCryptoModule(bool init
)
1899 mComposerBase
->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments());
1900 mComposerBase
->attachmentModel()->setSignEnabled(canSignEncryptAttachments());
1903 void KMComposerWin::slotUpdateFont()
1905 if (!mFixedFontAction
) {
1908 mComposerBase
->editor()->composerControler()->setFontForWholeText(mFixedFontAction
->isChecked() ?
1909 mFixedFont
: mBodyFont
);
1912 QUrl
KMComposerWin::insertFile()
1914 const KEncodingFileDialog::Result result
= KEncodingFileDialog::getOpenUrlAndEncoding(QString(),
1918 i18nc("@title:window", "Insert File"));
1920 if (!result
.URLs
.isEmpty()) {
1921 url
= result
.URLs
.first();
1922 if (url
.isValid()) {
1923 MessageCore::StringUtil::setEncodingFile(url
, MimeTreeParser::NodeHelper::fixEncoding(result
.encoding
));
1929 QString
KMComposerWin::smartQuote(const QString
&msg
)
1931 return MessageCore::StringUtil::smartQuote(msg
, MessageComposer::MessageComposerSettings::self()->lineWrapWidth());
1934 void KMComposerWin::insertUrls(const QMimeData
*source
, const QList
<QUrl
> &urlList
)
1936 QStringList urlAdded
;
1937 foreach (const QUrl
&url
, urlList
) {
1939 if (url
.scheme() == QLatin1String("mailto")) {
1940 urlStr
= KEmailAddress::decodeMailtoUrl(url
);
1942 urlStr
= url
.toDisplayString();
1943 // Workaround #346370
1944 if (urlStr
.isEmpty()) {
1945 urlStr
= source
->text();
1948 if (!urlAdded
.contains(urlStr
)) {
1949 mComposerBase
->editor()->composerControler()->insertLink(urlStr
);
1950 urlAdded
.append(urlStr
);
1956 bool KMComposerWin::insertFromMimeData(const QMimeData
*source
, bool forceAttachment
)
1958 // If this is a PNG image, either add it as an attachment or as an inline image
1959 if (source
->hasImage() && source
->hasFormat(QStringLiteral("image/png"))) {
1960 // Get the image data before showing the dialog, since that processes events which can delete
1961 // the QMimeData object behind our back
1962 const QByteArray imageData
= source
->data(QStringLiteral("image/png"));
1963 if (imageData
.isEmpty()) {
1966 if (!forceAttachment
) {
1967 if (mComposerBase
->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich
/*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
1968 QImage image
= qvariant_cast
<QImage
>(source
->imageData());
1969 QFileInfo
fi(source
->text());
1972 const QAction
*addAsInlineImageAction
= menu
.addAction(i18n("Add as &Inline Image"));
1973 /*const QAction *addAsAttachmentAction = */menu
.addAction(i18n("Add as &Attachment"));
1974 const QAction
*selectedAction
= menu
.exec(QCursor::pos());
1975 if (selectedAction
== addAsInlineImageAction
) {
1976 // Let the textedit from kdepimlibs handle inline images
1977 mComposerBase
->editor()->composerControler()->composerImages()->insertImage(image
, fi
);
1979 } else if (!selectedAction
) {
1982 // else fall through
1985 // Ok, when we reached this point, the user wants to add the image as an attachment.
1986 // Ask for the filename first.
1988 const QString attName
=
1989 QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal
, QString(), &ok
);
1993 addAttachment(attName
, KMime::Headers::CEbase64
, QString(), imageData
, "image/png");
1997 // If this is a URL list, add those files as attachments or text
1998 // but do not offer this if we are pasting plain text containing an url, e.g. from a browser
1999 const QList
<QUrl
> urlList
= source
->urls();
2000 if (!urlList
.isEmpty()) {
2001 if (source
->hasFormat(QStringLiteral("text/plain"))) {
2002 insertUrls(source
, urlList
);
2005 //Search if it's message items.
2006 Akonadi::Item::List items
;
2007 Akonadi::Collection::List collections
;
2008 bool allLocalURLs
= true;
2010 foreach (const QUrl
&url
, urlList
) {
2011 if (!url
.isLocalFile()) {
2012 allLocalURLs
= false;
2014 const Akonadi::Item item
= Akonadi::Item::fromUrl(url
);
2015 if (item
.isValid()) {
2018 const Akonadi::Collection collection
= Akonadi::Collection::fromUrl(url
);
2019 if (collection
.isValid()) {
2020 collections
<< collection
;
2025 if (items
.isEmpty() && collections
.isEmpty()) {
2026 if (allLocalURLs
|| forceAttachment
) {
2027 foreach (const QUrl
&url
, urlList
) {
2028 addAttachment(url
, QString());
2032 const int sizeUrl(urlList
.size());
2033 const QAction
*addAsTextAction
= p
.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl
));
2034 const QAction
*addAsAttachmentAction
= p
.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl
));
2035 const QAction
*selectedAction
= p
.exec(QCursor::pos());
2037 if (selectedAction
== addAsTextAction
) {
2038 insertUrls(source
, urlList
);
2039 } else if (selectedAction
== addAsAttachmentAction
) {
2040 foreach (const QUrl
&url
, urlList
) {
2041 if (url
.isValid()) {
2042 addAttachment(url
, QString());
2049 if (!items
.isEmpty()) {
2050 Akonadi::ItemFetchJob
*itemFetchJob
= new Akonadi::ItemFetchJob(items
, this);
2051 itemFetchJob
->fetchScope().fetchFullPayload(true);
2052 itemFetchJob
->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent
);
2053 connect(itemFetchJob
, &Akonadi::ItemFetchJob::result
, this, &KMComposerWin::slotFetchJob
);
2055 if (!collections
.isEmpty()) {
2056 qCDebug(KMAIL_LOG
) << "Collection dnd not supported";
2065 void KMComposerWin::slotPasteAsAttachment()
2067 const QMimeData
*mimeData
= QApplication::clipboard()->mimeData();
2068 if (insertFromMimeData(mimeData
, true)) {
2071 if (mimeData
->hasText()) {
2073 const QString attName
= QInputDialog::getText(this,
2074 i18n("Insert clipboard text as attachment"),
2075 i18n("Name of the attachment:"), QLineEdit::Normal
,
2078 mComposerBase
->addAttachment(attName
, attName
, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
2084 void KMComposerWin::slotFetchJob(KJob
*job
)
2086 if (showErrorMessage(job
)) {
2089 Akonadi::ItemFetchJob
*fjob
= qobject_cast
<Akonadi::ItemFetchJob
*>(job
);
2093 const Akonadi::Item::List items
= fjob
->items();
2095 if (items
.isEmpty()) {
2099 if (items
.first().mimeType() == KMime::Message::mimeType()) {
2101 if (items
.at(0).isValid() && items
.at(0).parentCollection().isValid()) {
2102 QSharedPointer
<MailCommon::FolderCollection
> fd(MailCommon::FolderCollection::forCollection(items
.at(0).parentCollection(), false));
2104 identity
= fd
->identity();
2107 KMCommand
*command
= new KMForwardAttachedCommand(this, items
, identity
, this);
2110 foreach (const Akonadi::Item
&item
, items
) {
2111 QString attachmentName
= QStringLiteral("attachment");
2112 if (item
.hasPayload
<KContacts::Addressee
>()) {
2113 const KContacts::Addressee contact
= item
.payload
<KContacts::Addressee
>();
2114 attachmentName
= contact
.realName() + QLatin1String(".vcf");
2115 //Workaround about broken kaddressbook fields.
2116 QByteArray data
= item
.payloadData();
2117 KContacts::adaptIMAttributes(data
);
2118 addAttachment(attachmentName
, KMime::Headers::CEbase64
, QString(), data
, "text/x-vcard");
2119 } else if (item
.hasPayload
<KContacts::ContactGroup
>()) {
2120 const KContacts::ContactGroup group
= item
.payload
<KContacts::ContactGroup
>();
2121 attachmentName
= group
.name() + QLatin1String(".vcf");
2122 Akonadi::ContactGroupExpandJob
*expandJob
= new Akonadi::ContactGroupExpandJob(group
, this);
2123 expandJob
->setProperty("groupName", attachmentName
);
2124 connect(expandJob
, &KJob::result
, this, &KMComposerWin::slotExpandGroupResult
);
2127 addAttachment(attachmentName
, KMime::Headers::CEbase64
, QString(), item
.payloadData(), item
.mimeType().toLatin1());
2133 void KMComposerWin::slotExpandGroupResult(KJob
*job
)
2135 Akonadi::ContactGroupExpandJob
*expandJob
= qobject_cast
<Akonadi::ContactGroupExpandJob
*>(job
);
2136 Q_ASSERT(expandJob
);
2138 const QString attachmentName
= expandJob
->property("groupName").toString();
2139 KContacts::VCardConverter converter
;
2140 const QByteArray groupData
= converter
.exportVCards(expandJob
->contacts(), KContacts::VCardConverter::v3_0
);
2141 if (!groupData
.isEmpty()) {
2142 addAttachment(attachmentName
, KMime::Headers::CEbase64
, QString(), groupData
, "text/x-vcard");
2146 void KMComposerWin::slotClose()
2151 void KMComposerWin::slotNewComposer()
2154 KMime::Message::Ptr
msg(new KMime::Message());
2156 MessageHelper::initHeader(msg
, KMKernel::self()->identityManager(), currentIdentity());
2157 TemplateParser::TemplateParser
parser(msg
, TemplateParser::TemplateParser::NewMessage
);
2158 parser
.setIdentityManager(KMKernel::self()->identityManager());
2159 parser
.process(msg
, mCollectionForNewMessage
);
2160 win
= new KMComposerWin(msg
, false, false, KMail::Composer::New
, currentIdentity());
2161 win
->setCollectionForNewMessage(mCollectionForNewMessage
);
2162 bool forceCursorPosition
= parser
.cursorPositionWasSet();
2163 if (forceCursorPosition
) {
2164 win
->setFocusToEditor();
2169 void KMComposerWin::slotUpdateWindowTitle()
2171 QString
s(mEdtSubject
->toPlainText());
2172 // Remove characters that show badly in most window decorations:
2173 // newlines tend to become boxes.
2175 setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')'));
2177 setWindowTitle(s
.replace(QLatin1Char('\n'), QLatin1Char(' ')));
2181 void KMComposerWin::slotEncryptToggled(bool on
)
2183 setEncryption(on
, true);
2184 updateSignatureAndEncryptionStateIndicators();
2187 void KMComposerWin::setEncryption(bool encrypt
, bool setByUser
)
2189 bool wasModified
= isModified();
2193 if (!mEncryptAction
->isEnabled()) {
2196 // check if the user wants to encrypt messages to himself and if he defined
2197 // an encryption key for the current identity
2198 else if (encrypt
&& encryptToSelf() && !mLastIdentityHasEncryptionKey
) {
2200 KMessageBox::sorry(this,
2201 i18n("<qt><p>You have requested that messages be "
2202 "encrypted to yourself, but the currently selected "
2203 "identity does not define an (OpenPGP or S/MIME) "
2204 "encryption key to use for this.</p>"
2205 "<p>Please select the key(s) to use "
2206 "in the identity configuration.</p>"
2208 i18n("Undefined Encryption Key"));
2209 setModified(wasModified
);
2214 // make sure the mEncryptAction is in the right state
2215 mEncryptAction
->setChecked(encrypt
);
2217 updateSignatureAndEncryptionStateIndicators();
2219 // show the appropriate icon
2221 mEncryptAction
->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt")));
2223 mEncryptAction
->setIcon(QIcon::fromTheme(QStringLiteral("document-decrypt")));
2226 // mark the attachments for (no) encryption
2227 if (canSignEncryptAttachments()) {
2228 mComposerBase
->attachmentModel()->setEncryptSelected(encrypt
);
2232 void KMComposerWin::slotSignToggled(bool on
)
2234 setSigning(on
, true);
2235 updateSignatureAndEncryptionStateIndicators();
2238 void KMComposerWin::setSigning(bool sign
, bool setByUser
)
2240 bool wasModified
= isModified();
2244 if (!mSignAction
->isEnabled()) {
2248 // check if the user defined a signing key for the current identity
2249 if (sign
&& !mLastIdentityHasSigningKey
) {
2251 KMessageBox::sorry(this,
2252 i18n("<qt><p>In order to be able to sign "
2253 "this message you first have to "
2254 "define the (OpenPGP or S/MIME) signing key "
2256 "<p>Please select the key to use "
2257 "in the identity configuration.</p>"
2259 i18n("Undefined Signing Key"));
2260 setModified(wasModified
);
2265 // make sure the mSignAction is in the right state
2266 mSignAction
->setChecked(sign
);
2269 updateSignatureAndEncryptionStateIndicators();
2271 // mark the attachments for (no) signing
2272 if (canSignEncryptAttachments()) {
2273 mComposerBase
->attachmentModel()->setSignSelected(sign
);
2277 void KMComposerWin::slotWordWrapToggled(bool on
)
2280 mComposerBase
->editor()->enableWordWrap(validateLineWrapWidth());
2286 int KMComposerWin::validateLineWrapWidth()
2288 int lineWrap
= MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
2289 if ((lineWrap
== 0) || (lineWrap
> 78)) {
2291 } else if (lineWrap
< 30) {
2297 void KMComposerWin::disableWordWrap()
2299 mComposerBase
->editor()->disableWordWrap();
2302 void KMComposerWin::forceDisableHtml()
2304 mForceDisableHtml
= true;
2305 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded
);
2306 markupAction
->setEnabled(false);
2307 // FIXME: Remove the toggle toolbar action somehow
2310 bool KMComposerWin::isComposing() const
2312 return mComposerBase
&& mComposerBase
->isComposing();
2315 void KMComposerWin::disableForgottenAttachmentsCheck()
2317 mCheckForForgottenAttachments
= false;
2320 void KMComposerWin::slotPrint()
2322 printComposer(false);
2325 void KMComposerWin::slotPrintPreview()
2327 printComposer(true);
2330 void KMComposerWin::printComposer(bool preview
)
2332 MessageComposer::Composer
*composer
= createSimpleComposer();
2333 mMiscComposers
.append(composer
);
2334 composer
->setProperty("preview", preview
);
2335 connect(composer
, &MessageComposer::Composer::result
, this, &KMComposerWin::slotPrintComposeResult
);
2339 void KMComposerWin::slotPrintComposeResult(KJob
*job
)
2341 const bool preview
= job
->property("preview").toBool();
2342 printComposeResult(job
, preview
);
2345 void KMComposerWin::printComposeResult(KJob
*job
, bool preview
)
2347 Q_ASSERT(dynamic_cast< MessageComposer::Composer
* >(job
));
2348 MessageComposer::Composer
*composer
= dynamic_cast< MessageComposer::Composer
* >(job
);
2349 Q_ASSERT(mMiscComposers
.contains(composer
));
2350 mMiscComposers
.removeAll(composer
);
2352 if (composer
->error() == MessageComposer::Composer::NoError
) {
2354 Q_ASSERT(composer
->resultMessages().size() == 1);
2355 Akonadi::Item printItem
;
2356 printItem
.setPayload
<KMime::Message::Ptr
>(composer
->resultMessages().first());
2357 Akonadi::MessageFlags::copyMessageFlags(*(composer
->resultMessages().first()), printItem
);
2358 const bool isHtml
= mComposerBase
->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich
;
2359 const MessageViewer::Viewer::DisplayFormatMessage format
= isHtml
? MessageViewer::Viewer::Html
: MessageViewer::Viewer::Text
;
2360 KMPrintCommand
*command
= new KMPrintCommand(this, printItem
, Q_NULLPTR
,
2362 command
->setPrintPreview(preview
);
2365 showErrorMessage(job
);
2370 void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method
,
2371 MessageComposer::MessageSender::SaveIn saveIn
)
2373 // TODO integrate with MDA online status
2374 if (method
== MessageComposer::MessageSender::SendImmediate
) {
2375 if (!MessageComposer::Util::sendMailDispatcherIsOnline()) {
2376 method
= MessageComposer::MessageSender::SendLater
;
2380 if (saveIn
== MessageComposer::MessageSender::SaveInNone
) { // don't save as draft or template, send immediately
2381 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2382 if (!(mShowHeaders
& HDR_FROM
)) {
2383 mShowHeaders
|= HDR_FROM
;
2384 rethinkFields(false);
2386 mEdtFrom
->setFocus();
2387 KMessageBox::sorry(this,
2388 i18n("You must enter your email address in the "
2389 "From: field. You should also set your email "
2390 "address for all identities, so that you do "
2391 "not have to enter it for each message."));
2394 if (mComposerBase
->to().isEmpty()) {
2395 if (mComposerBase
->cc().isEmpty() && mComposerBase
->bcc().isEmpty()) {
2396 KMessageBox::information(this,
2397 i18n("You must specify at least one receiver, "
2398 "either in the To: field or as CC or as BCC."));
2402 int rc
= KMessageBox::questionYesNo(this,
2403 i18n("To: field is empty. "
2404 "Send message anyway?"),
2405 i18n("No To: specified"),
2406 KStandardGuiItem::yes(),
2407 KStandardGuiItem::no(),
2408 QStringLiteral(":kmail_no_to_field_specified"));
2409 if (rc
== KMessageBox::No
) {
2415 if (subject().isEmpty()) {
2416 mEdtSubject
->setFocus();
2418 KMessageBox::questionYesNo(this,
2419 i18n("You did not specify a subject. "
2420 "Send message anyway?"),
2421 i18n("No Subject Specified"),
2422 KGuiItem(i18n("S&end as Is")),
2423 KGuiItem(i18n("&Specify the Subject")),
2424 QStringLiteral("no_subject_specified"));
2425 if (rc
== KMessageBox::No
) {
2430 const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment
= userForgotAttachment();
2431 if ((forgotAttachment
== MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment
) ||
2432 (forgotAttachment
== MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel
)) {
2435 MessageComposer::PluginEditorCheckBeforeSendParams params
;
2436 params
.setSubject(subject());
2437 params
.setHtmlMail(mComposerBase
->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich
);
2438 params
.setIdentity(mComposerBase
->identityCombo()->currentIdentity());
2439 params
.setHasAttachment(mComposerBase
->attachmentModel()->rowCount() > 0);
2440 params
.setTransportId(mComposerBase
->transportComboBox()->currentTransportId());
2441 const KIdentityManagement::Identity
&ident
= KMKernel::self()->identityManager()->identityForUoid(mComposerBase
->identityCombo()->currentIdentity());
2442 QString defaultDomainName
;
2443 if (!ident
.isNull()) {
2444 defaultDomainName
= ident
.defaultDomainName();
2446 params
.setBccAddresses(mComposerBase
->bcc().trimmed());
2447 params
.setToAddresses(mComposerBase
->to().trimmed());
2448 params
.setCcAddresses(mComposerBase
->cc().trimmed());
2449 params
.setDefaultDomain(defaultDomainName
);
2451 if (!mPluginEditorCheckBeforeSendManagerInterface
->execute(params
)) {
2454 const QStringList recipients
= { mComposerBase
->to().trimmed(), mComposerBase
->cc().trimmed(), mComposerBase
->bcc().trimmed()};
2458 // Validate the To:, CC: and BCC fields
2459 AddressValidationJob
*job
= new AddressValidationJob(recipients
.join(QStringLiteral(", ")), this, this);
2460 job
->setDefaultDomain(defaultDomainName
);
2461 job
->setProperty("method", static_cast<int>(method
));
2462 job
->setProperty("saveIn", static_cast<int>(saveIn
));
2463 connect(job
, &Akonadi::ItemFetchJob::result
, this, &KMComposerWin::slotDoDelayedSend
);
2466 // we'll call send from within slotDoDelaySend
2468 if (saveIn
== MessageComposer::MessageSender::SaveInDrafts
&& mEncryptAction
->isChecked() &&
2469 !KMailSettings::self()->neverEncryptDrafts() &&
2470 mComposerBase
->to().isEmpty() && mComposerBase
->cc().isEmpty()) {
2472 KMessageBox::information(this, i18n("You must specify at least one receiver "
2473 "in order to be able to encrypt a draft.")
2477 doDelayedSend(method
, saveIn
);
2481 void KMComposerWin::slotDoDelayedSend(KJob
*job
)
2484 KMessageBox::error(this, job
->errorText());
2489 const AddressValidationJob
*validateJob
= qobject_cast
<AddressValidationJob
*>(job
);
2491 // Abort sending if one of the recipient addresses is invalid ...
2492 if (!validateJob
->isValid()) {
2497 // ... otherwise continue as usual
2498 const MessageComposer::MessageSender::SendMethod method
= static_cast<MessageComposer::MessageSender::SendMethod
>(job
->property("method").toInt());
2499 const MessageComposer::MessageSender::SaveIn saveIn
= static_cast<MessageComposer::MessageSender::SaveIn
>(job
->property("saveIn").toInt());
2501 doDelayedSend(method
, saveIn
);
2504 void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase
*mComposerBase
)
2507 QList
< QByteArray
> charsets
= mCodecAction
->mimeCharsets();
2508 if (!mOriginalPreferredCharset
.isEmpty()) {
2509 charsets
.insert(0, mOriginalPreferredCharset
);
2511 mComposerBase
->setFrom(from());
2512 mComposerBase
->setReplyTo(replyTo());
2513 mComposerBase
->setSubject(subject());
2514 mComposerBase
->setCharsets(charsets
);
2515 mComposerBase
->setUrgent(mUrgentAction
->isChecked());
2516 mComposerBase
->setMDNRequested(mRequestMDNAction
->isChecked());
2519 void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method
, MessageComposer::MessageSender::SaveIn saveIn
)
2521 #ifndef QT_NO_CURSOR
2522 KPIM::KCursorSaver
busy(KPIM::KBusyPtr::busy());
2524 applyComposerSetting(mComposerBase
);
2525 if (mForceDisableHtml
) {
2526 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded
);
2528 bool sign
= mSignAction
->isChecked();
2529 bool encrypt
= mEncryptAction
->isChecked();
2531 mComposerBase
->setCryptoOptions(sign
, encrypt
, cryptoMessageFormat(),
2532 ((saveIn
!= MessageComposer::MessageSender::SaveInNone
&& KMailSettings::self()->neverEncryptDrafts())
2533 || mSigningAndEncryptionExplicitlyDisabled
));
2535 const int num
= KMailSettings::self()->customMessageHeadersCount();
2536 QMap
<QByteArray
, QString
> customHeader
;
2537 for (int ix
= 0; ix
< num
; ++ix
) {
2538 CustomMimeHeader
customMimeHeader(QString::number(ix
));
2539 customMimeHeader
.load();
2540 customHeader
.insert(customMimeHeader
.custHeaderName().toLatin1(), customMimeHeader
.custHeaderValue());
2543 QMapIterator
<QByteArray
, QString
> extraCustomHeader(mExtraHeaders
);
2544 while (extraCustomHeader
.hasNext()) {
2545 extraCustomHeader
.next();
2546 customHeader
.insert(extraCustomHeader
.key(), extraCustomHeader
.value());
2549 mComposerBase
->setCustomHeader(customHeader
);
2550 mComposerBase
->send(method
, saveIn
, false);
2553 void KMComposerWin::slotSendLater()
2555 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists
)) {
2558 if (!checkRecipientNumber()) {
2561 if (mComposerBase
->editor()->checkExternalEditorFinished()) {
2562 const bool wasRegistered
= (SendLater::SendLaterUtil::sentLaterAgentWasRegistered() && SendLater::SendLaterUtil::sentLaterAgentEnabled());
2563 if (wasRegistered
) {
2564 SendLater::SendLaterInfo
*info
= Q_NULLPTR
;
2565 QPointer
<SendLater::SendLaterDialog
> dlg
= new SendLater::SendLaterDialog(info
, this);
2568 const SendLater::SendLaterDialog::SendLaterAction action
= dlg
->action();
2571 case SendLater::SendLaterDialog::Unknown
:
2572 qCDebug(KMAIL_LOG
) << "Sendlater action \"Unknown\": Need to fix it.";
2574 case SendLater::SendLaterDialog::Canceled
:
2577 case SendLater::SendLaterDialog::PutInOutbox
:
2578 doSend(MessageComposer::MessageSender::SendLater
);
2580 case SendLater::SendLaterDialog::SendDeliveryAtTime
: {
2581 mComposerBase
->setSendLaterInfo(info
);
2582 if (info
->isRecurrence()) {
2583 doSend(MessageComposer::MessageSender::SendLater
, MessageComposer::MessageSender::SaveInTemplates
);
2585 doSend(MessageComposer::MessageSender::SendLater
, MessageComposer::MessageSender::SaveInDrafts
);
2594 doSend(MessageComposer::MessageSender::SendLater
);
2599 void KMComposerWin::slotSaveDraft()
2601 if (mComposerBase
->editor()->checkExternalEditorFinished()) {
2602 doSend(MessageComposer::MessageSender::SendLater
, MessageComposer::MessageSender::SaveInDrafts
);
2606 void KMComposerWin::slotSaveTemplate()
2608 if (mComposerBase
->editor()->checkExternalEditorFinished()) {
2609 doSend(MessageComposer::MessageSender::SendLater
, MessageComposer::MessageSender::SaveInTemplates
);
2613 void KMComposerWin::slotSendNowVia(MailTransport::Transport
*transport
)
2616 mComposerBase
->transportComboBox()->setCurrentTransport(transport
->id());
2621 void KMComposerWin::slotSendLaterVia(MailTransport::Transport
*transport
)
2624 mComposerBase
->transportComboBox()->setCurrentTransport(transport
->id());
2629 void KMComposerWin::sendNow(bool shortcutUsed
)
2631 if (!mComposerBase
->editor()->checkExternalEditorFinished()) {
2634 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists
)) {
2637 if (!checkRecipientNumber()) {
2640 mSendNowByShortcutUsed
= shortcutUsed
;
2641 if (KMailSettings::self()->checkSpellingBeforeSend()) {
2642 mComposerBase
->editor()->forceSpellChecking();
2648 void KMComposerWin::slotSendNowByShortcut()
2653 void KMComposerWin::slotSendNow()
2658 void KMComposerWin::confirmBeforeSend()
2660 const int rc
= KMessageBox::warningYesNoCancel(mMainWidget
,
2661 i18n("About to send email..."),
2662 i18n("Send Confirmation"),
2663 KGuiItem(i18n("&Send Now")),
2664 KGuiItem(i18n("Send &Later")));
2666 if (rc
== KMessageBox::Yes
) {
2667 doSend(MessageComposer::MessageSender::SendImmediate
);
2668 } else if (rc
== KMessageBox::No
) {
2669 doSend(MessageComposer::MessageSender::SendLater
);
2673 void KMComposerWin::slotCheckSendNowStep2()
2675 if (KMailSettings::self()->confirmBeforeSend()) {
2676 confirmBeforeSend();
2678 if (mSendNowByShortcutUsed
) {
2679 if (!KMailSettings::self()->checkSendDefaultActionShortcut()) {
2680 ValidateSendMailShortcut
validateShortcut(actionCollection(), this);
2681 if (!validateShortcut
.validate()) {
2685 if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) {
2686 confirmBeforeSend();
2690 doSend(MessageComposer::MessageSender::SendImmediate
);
2694 void KMComposerWin::slotCheckSendNow()
2696 PotentialPhishingEmailJob
*job
= new PotentialPhishingEmailJob(this);
2697 KConfigGroup
group(KSharedConfig::openConfig(), "PotentialPhishing");
2698 const QStringList whiteList
= group
.readEntry("whiteList", QStringList());
2699 job
->setEmailWhiteList(whiteList
);
2701 lst
<< mComposerBase
->to();
2702 if (!mComposerBase
->cc().isEmpty()) {
2703 lst
<< mComposerBase
->cc().split(QLatin1Char(','));
2705 if (!mComposerBase
->bcc().isEmpty()) {
2706 lst
<< mComposerBase
->bcc().split(QLatin1Char(','));
2708 job
->setEmails(lst
);
2709 connect(job
, &PotentialPhishingEmailJob::potentialPhishingEmailsFound
, this, &KMComposerWin::slotPotentialPhishingEmailsFound
);
2713 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList
&list
)
2715 if (list
.isEmpty()) {
2716 slotCheckSendNowStep2();
2718 mPotentialPhishingEmailWarning
->setPotentialPhisingEmail(list
);
2722 bool KMComposerWin::checkRecipientNumber() const
2724 const int thresHold
= KMailSettings::self()->recipientThreshold();
2725 if (KMailSettings::self()->tooManyRecipients() && mComposerBase
->recipientsEditor()->recipients().count() > thresHold
) {
2726 if (KMessageBox::questionYesNo(mMainWidget
,
2727 i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold
),
2728 i18n("Too many recipients"),
2729 KGuiItem(i18n("&Send as Is")),
2730 KGuiItem(i18n("&Edit Recipients"))) == KMessageBox::No
) {
2737 void KMComposerWin::enableHtml()
2739 if (mForceDisableHtml
) {
2740 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded
);
2744 mComposerBase
->editor()->activateRichText();
2745 if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
2746 // Use singleshot, as we we might actually be called from a slot that wanted to disable the
2747 // toolbar (but the messagebox in disableHtml() prevented that and called us).
2748 // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
2749 // signal, so wait one event loop run for that.
2750 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show
);
2752 if (!markupAction
->isChecked()) {
2753 markupAction
->setChecked(true);
2756 mComposerBase
->editor()->composerActions()->updateActionStates();
2757 mComposerBase
->editor()->composerActions()->setActionsEnabled(true);
2760 void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation
)
2762 bool forcePlainTextMarkup
= false;
2763 if (confirmation
== MessageComposer::ComposerViewBase::LetUserConfirm
&& mComposerBase
->editor()->composerControler()->isFormattingUsed() && !mForceDisableHtml
) {
2764 int choice
= KMessageBox::warningYesNoCancel(this, i18n("Turning HTML mode off "
2765 "will cause the text to lose the formatting. Are you sure?"),
2766 i18n("Lose the formatting?"), KGuiItem(i18n("Lose Formatting")), KGuiItem(i18n("Add Markup Plain Text")), KStandardGuiItem::cancel(),
2767 QStringLiteral("LoseFormattingWarning"));
2770 case KMessageBox::Cancel
:
2773 case KMessageBox::No
:
2774 forcePlainTextMarkup
= true;
2776 case KMessageBox::Yes
:
2781 mComposerBase
->editor()->forcePlainTextMarkup(forcePlainTextMarkup
);
2782 mComposerBase
->editor()->switchToPlainText();
2783 mComposerBase
->editor()->composerActions()->setActionsEnabled(false);
2786 if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
2787 // See the comment in enableHtml() why we use a singleshot timer, similar situation here.
2788 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide
);
2790 if (markupAction
->isChecked()) {
2791 markupAction
->setChecked(false);
2795 void KMComposerWin::slotToggleMarkup()
2797 htmlToolBarVisibilityChanged(markupAction
->isChecked());
2800 void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode
)
2802 if (mode
== MessageComposer::RichTextComposerNg::Plain
) {
2803 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded
); // ### Can this happen at all?
2809 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible
)
2814 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm
);
2818 void KMComposerWin::slotAutoSpellCheckingToggled(bool on
)
2820 mAutoSpellCheckingAction
->setChecked(on
);
2821 if (on
!= mComposerBase
->editor()->checkSpellingEnabled()) {
2822 mComposerBase
->editor()->setCheckSpellingEnabled(on
);
2824 if (on
!= mEdtSubject
->checkSpellingEnabled()) {
2825 mEdtSubject
->setCheckSpellingEnabled(on
);
2827 mStatusBarLabelSpellCheckingChangeMode
->setToggleMode(on
);
2830 void KMComposerWin::slotSpellCheckingStatus(const QString
&status
)
2832 mStatusBarLabelList
.at(0)->setText(status
);
2833 QTimer::singleShot(2000, this, &KMComposerWin::slotSpellcheckDoneClearStatus
);
2836 void KMComposerWin::slotSpellcheckDoneClearStatus()
2838 mStatusBarLabelList
.at(0)->clear();
2841 void KMComposerWin::slotIdentityChanged(uint uoid
, bool initalChange
)
2843 if (mMsg
== Q_NULLPTR
) {
2844 qCDebug(KMAIL_LOG
) << "Trying to change identity but mMsg == 0!";
2848 const KIdentityManagement::Identity
&ident
=
2849 KMKernel::self()->identityManager()->identityForUoid(uoid
);
2850 if (ident
.isNull()) {
2853 bool wasModified(isModified());
2854 Q_EMIT
identityChanged(identity());
2855 if (!ident
.fullEmailAddr().isNull()) {
2856 mEdtFrom
->setText(ident
.fullEmailAddr());
2859 // make sure the From field is shown if it does not contain a valid email address
2860 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) {
2861 mShowHeaders
|= HDR_FROM
;
2864 mEdtReplyTo
->setText(ident
.replyToAddr());
2867 // remove BCC of old identity and add BCC of new identity (if they differ)
2868 const KIdentityManagement::Identity
&oldIdentity
=
2869 KMKernel::self()->identityManager()->identityForUoidOrDefault(mId
);
2871 if (ident
.organization().isEmpty()) {
2872 mMsg
->removeHeader
<KMime::Headers::Organization
>();
2874 KMime::Headers::Organization
*const organization
= new KMime::Headers::Organization
;
2875 organization
->fromUnicodeString(ident
.organization(), "utf-8");
2876 mMsg
->setHeader(organization
);
2878 if (!ident
.isXFaceEnabled() || ident
.xface().isEmpty()) {
2879 mMsg
->removeHeader("X-Face");
2881 QString xface
= ident
.xface();
2882 if (!xface
.isEmpty()) {
2883 int numNL
= (xface
.length() - 1) / 70;
2884 for (int i
= numNL
; i
> 0; --i
) {
2885 xface
.insert(i
* 70, QStringLiteral("\n\t"));
2887 KMime::Headers::Generic
*header
= new KMime::Headers::Generic("X-Face");
2888 header
->fromUnicodeString(xface
, "utf-8");
2889 mMsg
->setHeader(header
);
2892 const int transportId
= ident
.transport().isEmpty() ? -1 : ident
.transport().toInt();
2893 const Transport
*transport
= TransportManager::self()->transportById(transportId
, true);
2895 mMsg
->removeHeader("X-KMail-Transport");
2896 mComposerBase
->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId());
2898 KMime::Headers::Generic
*header
= new KMime::Headers::Generic("X-KMail-Transport");
2899 header
->fromUnicodeString(QString::number(transport
->id()), "utf-8");
2900 mMsg
->setHeader(header
);
2901 mComposerBase
->transportComboBox()->setCurrentTransport(transport
->id());
2904 const bool fccIsDisabled
= ident
.disabledFcc();
2905 if (fccIsDisabled
) {
2906 KMime::Headers::Generic
*header
= new KMime::Headers::Generic("X-KMail-FccDisabled");
2907 header
->fromUnicodeString(QStringLiteral("true"), "utf-8");
2908 mMsg
->setHeader(header
);
2910 mMsg
->removeHeader("X-KMail-FccDisabled");
2912 mFccFolder
->setEnabled(!fccIsDisabled
);
2914 mComposerBase
->dictionary()->setCurrentByDictionaryName(ident
.dictionary());
2915 slotSpellCheckingLanguage(mComposerBase
->dictionary()->currentDictionary());
2916 if (!mPreventFccOverwrite
) {
2917 setFcc(ident
.fcc());
2919 // if unmodified, apply new template, if one is set
2920 if (!wasModified
&& !(ident
.templates().isEmpty() && mCustomTemplate
.isEmpty()) &&
2922 applyTemplate(uoid
, mId
);
2924 mComposerBase
->identityChanged(ident
, oldIdentity
, false);
2925 mEdtSubject
->setAutocorrectionLanguage(ident
.autocorrectionLanguage());
2928 // disable certain actions if there is no PGP user identity set
2930 bool bNewIdentityHasSigningKey
= !ident
.pgpSigningKey().isEmpty() || !ident
.smimeSigningKey().isEmpty();
2931 bool bNewIdentityHasEncryptionKey
= !ident
.pgpSigningKey().isEmpty() || !ident
.smimeSigningKey().isEmpty();
2932 // save the state of the sign and encrypt button
2933 if (!bNewIdentityHasEncryptionKey
&& mLastIdentityHasEncryptionKey
) {
2934 mLastEncryptActionState
= mEncryptAction
->isChecked();
2935 setEncryption(false);
2937 if (!bNewIdentityHasSigningKey
&& mLastIdentityHasSigningKey
) {
2938 mLastSignActionState
= mSignAction
->isChecked();
2941 // restore the last state of the sign and encrypt button
2942 if (bNewIdentityHasEncryptionKey
&& !mLastIdentityHasEncryptionKey
) {
2943 setEncryption(mLastEncryptActionState
);
2945 if (bNewIdentityHasSigningKey
&& !mLastIdentityHasSigningKey
) {
2946 setSigning(mLastSignActionState
);
2949 mCryptoModuleAction
->setCurrentItem(format2cb(
2950 Kleo::stringToCryptoMessageFormat(ident
.preferredCryptoMessageFormat())));
2951 slotSelectCryptoModule(true);
2953 mLastIdentityHasSigningKey
= bNewIdentityHasSigningKey
;
2954 mLastIdentityHasEncryptionKey
= bNewIdentityHasEncryptionKey
;
2955 const KIdentityManagement::Signature sig
= const_cast<KIdentityManagement::Identity
&>(ident
).signature();
2956 bool isEnabledSignature
= sig
.isEnabledSignature();
2957 mAppendSignature
->setEnabled(isEnabledSignature
);
2958 mPrependSignature
->setEnabled(isEnabledSignature
);
2959 mInsertSignatureAtCursorPosition
->setEnabled(isEnabledSignature
);
2962 changeCryptoAction();
2963 // make sure the From and BCC fields are shown if necessary
2964 rethinkFields(false);
2965 setModified(wasModified
);
2968 void KMComposerWin::slotSpellcheckConfig()
2970 static_cast<KMComposerEditorNg
*>(mComposerBase
->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc"));
2973 void KMComposerWin::slotEditToolbars()
2975 KConfigGroup
grp(KMKernel::self()->config()->group("Composer"));
2976 saveMainWindowSettings(grp
);
2977 KEditToolBar
dlg(guiFactory(), this);
2979 connect(&dlg
, &KEditToolBar::newToolBarConfig
, this, &KMComposerWin::slotUpdateToolbars
);
2984 void KMComposerWin::slotUpdateToolbars()
2986 createGUI(QStringLiteral("kmcomposerui.rc"));
2987 applyMainWindowSettings(KMKernel::self()->config()->group("Composer"));
2990 void KMComposerWin::slotEditKeys()
2992 KShortcutsDialog::configure(actionCollection(),
2993 KShortcutsEditor::LetterShortcutsDisallowed
);
2996 void KMComposerWin::setFocusToEditor()
2998 // The cursor position is already set by setMsg(), so we only need to set the
3000 mComposerBase
->editor()->setFocus();
3003 void KMComposerWin::setFocusToSubject()
3005 mEdtSubject
->setFocus();
3008 void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode
)
3010 KMailSettings::self()->setCompletionMode((int) mode
);
3012 // sync all the lineedits to the same completion mode
3013 mEdtFrom
->setCompletionMode(mode
);
3014 mEdtReplyTo
->setCompletionMode(mode
);
3015 mComposerBase
->recipientsEditor()->setCompletionMode(mode
);
3018 void KMComposerWin::slotConfigChanged()
3020 readConfig(true /*reload*/);
3021 mComposerBase
->updateAutoSave();
3023 slotWordWrapToggled(mWordWrapAction
->isChecked());
3027 * checks if the drafts-folder has been deleted
3028 * that is not nice so we set the system-drafts-folder
3030 void KMComposerWin::slotFolderRemoved(const Akonadi::Collection
&col
)
3032 qCDebug(KMAIL_LOG
) << "you killed me.";
3033 // TODO: need to handle templates here?
3034 if ((mFolder
.isValid()) && (col
.id() == mFolder
.id())) {
3035 mFolder
= CommonKernel
->draftsCollectionFolder();
3036 qCDebug(KMAIL_LOG
) << "restoring drafts to" << mFolder
.id();
3040 void KMComposerWin::slotOverwriteModeChanged()
3042 const bool overwriteMode
= mComposerBase
->editor()->overwriteMode();
3043 mComposerBase
->editor()->setCursorWidth(overwriteMode
? 5 : 1);
3044 mStatusBarLabelToggledOverrideMode
->setToggleMode(overwriteMode
);
3047 void KMComposerWin::slotCursorPositionChanged()
3049 // Change Line/Column info in status bar
3050 const int line
= mComposerBase
->editor()->linePosition();
3051 const int col
= mComposerBase
->editor()->columnNumber();
3052 QString temp
= i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line
+ 1);
3053 mStatusBarLabelList
.at(1)->setText(temp
);
3054 temp
= i18n(" Column: %1 ", col
+ 1);
3055 mStatusBarLabelList
.at(2)->setText(temp
);
3057 // Show link target in status bar
3058 if (mComposerBase
->editor()->textCursor().charFormat().isAnchor()) {
3059 const QString text
= mComposerBase
->editor()->composerControler()->currentLinkText();
3060 const QString url
= mComposerBase
->editor()->composerControler()->currentLinkUrl();
3061 mStatusBarLabelList
.at(0)->setText(text
+ QLatin1String(" -> ") + url
);
3063 mStatusBarLabelList
.at(0)->clear();
3067 void KMComposerWin::recipientEditorSizeHintChanged()
3069 QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize
);
3072 void KMComposerWin::setMaximumHeaderSize()
3074 mHeadersArea
->setMaximumHeight(mHeadersArea
->sizeHint().height());
3077 void KMComposerWin::updateSignatureAndEncryptionStateIndicators()
3079 mCryptoStateIndicatorWidget
->updateSignatureAndEncrypionStateIndicators(mSignAction
->isChecked(), mEncryptAction
->isChecked());
3082 void KMComposerWin::slotDictionaryLanguageChanged(const QString
&language
)
3084 mComposerBase
->dictionary()->setCurrentByDictionary(language
);
3087 void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection
&collection
)
3089 mComposerBase
->setFcc(collection
);
3090 mComposerBase
->editor()->document()->setModified(true);
3093 void KMComposerWin::slotSaveAsFile()
3095 SaveAsFileJob
*job
= new SaveAsFileJob(this);
3096 job
->setParentWidget(this);
3097 job
->setHtmlMode(mComposerBase
->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich
);
3098 job
->setTextDocument(mComposerBase
->editor()->document());
3100 //not necessary to delete it. It done in SaveAsFileJob
3103 void KMComposerWin::slotAttachMissingFile()
3105 mComposerBase
->attachmentController()->showAddAttachmentFileDialog();
3108 void KMComposerWin::slotVerifyMissingAttachmentTimeout()
3110 if (mComposerBase
->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) {
3111 mAttachmentMissing
->animatedShow();
3115 void KMComposerWin::slotExplicitClosedMissingAttachment()
3117 if (m_verifyMissingAttachment
) {
3118 m_verifyMissingAttachment
->stop();
3119 delete m_verifyMissingAttachment
;
3120 m_verifyMissingAttachment
= Q_NULLPTR
;
3124 void KMComposerWin::addExtraCustomHeaders(const QMap
<QByteArray
, QString
> &headers
)
3126 mExtraHeaders
= headers
;
3129 void KMComposerWin::slotExternalEditorStarted()
3131 mComposerBase
->identityCombo()->setEnabled(false);
3132 mExternalEditorWarning
->show();
3135 void KMComposerWin::slotExternalEditorClosed()
3137 mComposerBase
->identityCombo()->setEnabled(true);
3138 mExternalEditorWarning
->hide();
3141 void KMComposerWin::slotInsertShortUrl(const QString
&url
)
3143 mComposerBase
->editor()->composerControler()->insertLink(url
);
3146 void KMComposerWin::slotShareLinkDone(const QString
&link
)
3148 mComposerBase
->editor()->composerControler()->insertShareLink(link
);
3151 void KMComposerWin::slotTransportChanged()
3153 mComposerBase
->editor()->document()->setModified(true);
3156 void KMComposerWin::slotFollowUpMail(bool toggled
)
3159 QPointer
<MessageComposer::FollowUpReminderSelectDateDialog
> dlg
= new MessageComposer::FollowUpReminderSelectDateDialog(this);
3161 mComposerBase
->setFollowUpDate(dlg
->selectedDate());
3162 mComposerBase
->setFollowUpCollection(dlg
->collection());
3164 mFollowUpToggleAction
->setChecked(false);
3168 mComposerBase
->clearFollowUp();
3172 void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b
)
3174 mSnippetWidget
->setVisible(b
);
3175 mSnippetSplitterCollapser
->setVisible(b
);
3178 void KMComposerWin::slotOverwriteModeWasChanged(bool state
)
3180 mComposerBase
->editor()->setCursorWidth(state
? 5 : 1);
3181 mComposerBase
->editor()->setOverwriteMode(state
);
3184 QList
<KToggleAction
*> KMComposerWin::customToolsList() const
3186 return mCustomToolsWidget
->actionList();
3189 QList
<QAction
*> KMComposerWin::pluginToolsActionListForPopupMenu() const
3191 return mPluginEditorManagerInterface
->actionsType(MessageComposer::ActionType::PopupMenu
);
3194 void KMComposerWin::slotInsertNonBreakingSpace()
3196 mComposerBase
->editor()->insertPlainText(QChar(0x000A0));