Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34348712
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
95 KB
Subscribers
None
View Options
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
index 046b86f..3469600 100644
--- a/server/CMakeLists.txt
+++ b/server/CMakeLists.txt
@@ -1,289 +1,287 @@
# SPDX-FileCopyrightText: 2023 g10 code GmbH
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
add_library(gpgol-server-static STATIC)
target_sources(gpgol-server-static PRIVATE
websocketclient.cpp
websocketclient.h
webserver.h
webserver.cpp
# Identity
identity/addressvalidationjob.cpp
identity/addressvalidationjob.h
identity/identitymanager.cpp
identity/identitymanager.h
identity/identitydialog.cpp
identity/identitydialog.h
identity/identityaddvcarddialog.cpp
identity/identityaddvcarddialog.h
# HTTP Controller
controllers/emailcontroller.cpp
controllers/emailcontroller.h
# Draft
draft/draft.cpp
draft/draft.h
draft/draftmanager.cpp
draft/draftmanager.h
# EWS integration
ews/ewsattachment.cpp
ews/ewsattachment.h
ews/ewsattendee.cpp
ews/ewsattendee.h
ews/ewsclient_debug.cpp
ews/ewsclient_debug.h
ews/ewsid.cpp
ews/ewsid.h
ews/ewsitem.cpp
ews/ewsitem.h
ews/ewsitembase.cpp
ews/ewsitembase.h
ews/ewsitembase_p.h
ews/ewsmailbox.cpp
ews/ewsmailbox.h
ews/ewsmailfactory.cpp
ews/ewsmailfactory.h
ews/ewsoccurrence.cpp
ews/ewsoccurrence.h
ews/ewspropertyfield.cpp
ews/ewspropertyfield.h
ews/ewsrecurrence.cpp
ews/ewsrecurrence.h
ews/ewsserverversion.cpp
ews/ewsserverversion.h
ews/ewstypes.cpp
ews/ewstypes.h
ews/ewsxml.cpp
ews/ewsxml.h
# Editor
editor/addresseelineedit.cpp
editor/addresseelineedit.h
editor/addresseelineeditmanager.cpp
editor/addresseelineeditmanager.h
editor/composer.cpp
editor/composer.h
editor/composerviewbase.cpp
editor/composerviewbase.h
editor/composerwindow.cpp
editor/composerwindow.h
editor/composerwindowfactory.cpp
editor/composerwindowfactory.h
editor/cryptostateindicatorwidget.cpp
editor/cryptostateindicatorwidget.h
- editor/encryptionstate.cpp
- editor/encryptionstate.h
editor/kmcomposerglobalaction.cpp
editor/kmcomposerglobalaction.h
editor/nearexpirywarning.cpp
editor/nearexpirywarning.h
editor/mailtemplates.cpp
editor/mailtemplates.h
editor/recipient.cpp
editor/recipient.h
editor/recipientline.cpp
editor/recipientline.h
editor/recipientseditor.cpp
editor/recipientseditor.h
editor/util.h
editor/util.cpp
editor/kmailcompletion.cpp
editor/kmailcompletion.h
editor/richtextcomposerng.cpp
editor/richtextcomposerng.h
editor/richtextcomposersignatures.cpp
editor/richtextcomposersignatures.h
editor/nodehelper.cpp
editor/nodehelper.h
editor/signaturecontroller.cpp
editor/signaturecontroller.h
editor/spellcheckerconfigdialog.cpp
editor/spellcheckerconfigdialog.h
# Editor job
editor/job/abstractencryptjob.h
editor/job/autocryptheadersjob.h
editor/job/contentjobbase.h
editor/job/contentjobbase_p.h
editor/job/dndfromarkjob.cpp
editor/job/dndfromarkjob.h
editor/job/encryptjob.h
editor/job/inserttextfilejob.h
editor/job/itipjob.h
editor/job/jobbase.h
editor/job/jobbase_p.h
editor/job/maintextjob.h
editor/job/multipartjob.h
editor/job/protectedheadersjob.h
editor/job/signencryptjob.h
editor/job/signjob.h
editor/job/singlepartjob.h
editor/job/skeletonmessagejob.h
editor/job/transparentjob.h
editor/job/autocryptheadersjob.cpp
editor/job/contentjobbase.cpp
editor/job/encryptjob.cpp
editor/job/inserttextfilejob.cpp
editor/job/itipjob.cpp
editor/job/jobbase.cpp
editor/job/maintextjob.cpp
editor/job/multipartjob.cpp
editor/job/protectedheadersjob.cpp
editor/job/saveasfilejob.cpp
editor/job/saveasfilejob.h
editor/job/signencryptjob.cpp
editor/job/signjob.cpp
editor/job/singlepartjob.cpp
editor/job/skeletonmessagejob.cpp
editor/job/transparentjob.cpp
## Editor Part
editor/part/globalpart.h
editor/part/infopart.h
editor/part/itippart.h
editor/part/messagepart.h
editor/part/textpart.h
editor/part/globalpart.cpp
editor/part/infopart.cpp
editor/part/itippart.cpp
editor/part/messagepart.cpp
editor/part/textpart.cpp
## Attachment
editor/attachment/attachmentjob.cpp
editor/attachment/attachmentjob.h
editor/attachment/attachmentclipboardjob.cpp
editor/attachment/attachmentclipboardjob.h
editor/attachment/attachmentcompressjob.cpp
editor/attachment/attachmentcompressjob.h
editor/attachment/attachmentcontroller.cpp
editor/attachment/attachmentcontroller.h
editor/attachment/attachmentcontrollerbase.cpp
editor/attachment/attachmentcontrollerbase.h
editor/attachment/attachmentfromfolderjob.cpp
editor/attachment/attachmentfromfolderjob.h
editor/attachment/attachmentfrommimecontentjob.cpp
editor/attachment/attachmentfrommimecontentjob.h
editor/attachment/attachmentfromurlbasejob.cpp
editor/attachment/attachmentfromurlbasejob.h
editor/attachment/attachmentfromurljob.cpp
editor/attachment/attachmentfromurljob.h
editor/attachment/attachmentfromurlutils.cpp
editor/attachment/attachmentfromurlutils.h
editor/attachment/attachmentfrompublickeyjob.cpp
editor/attachment/attachmentfrompublickeyjob.h
editor/attachment/attachmentloadjob.cpp
editor/attachment/attachmentloadjob.h
editor/attachment/attachmentmodel.cpp
editor/attachment/attachmentmodel.h
editor/attachment/attachmentpart.cpp
editor/attachment/attachmentpart.h
editor/attachment/attachmentpropertiesdialog.cpp
editor/attachment/attachmentpropertiesdialog.h
editor/attachment/attachmentupdatejob.cpp
editor/attachment/attachmentupdatejob.h
editor/attachment/attachmentview.cpp
editor/attachment/attachmentview.h
editor/attachment/editorwatcher.cpp
editor/attachment/editorwatcher.h
)
qt_add_resources(gpgol-server-static
PREFIX
"/"
FILES
assets/certificate.crt
)
ki18n_wrap_ui(gpgol-server-static
editor/attachment/ui/attachmentpropertiesdialog.ui
editor/attachment/ui/attachmentpropertiesdialog_readonly.ui
)
ecm_qt_declare_logging_category(gpgol-server-static_SRCS
HEADER websocket_debug.h
IDENTIFIER WEBSOCKET_LOG
CATEGORY_NAME org.gpgol.server.websocket
DESCRIPTION "Websocket connection in the server"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-server-static_SRCS
HEADER ewsresource_debug.h
IDENTIFIER EWSRES_LOG
CATEGORY_NAME org.gpgol.ews
DESCRIPTION "Ews mail client"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-server-static_SRCS
HEADER ewscli_debug.h
IDENTIFIER EWSCLI_LOG
CATEGORY_NAME org.gpgol.ews.client
DESCRIPTION "ews client (gpgol-server)"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-server-static_SRCS
HEADER editor_debug.h
IDENTIFIER EDITOR_LOG
CATEGORY_NAME org.gpgol.editor
DESCRIPTION "mail composer"
EXPORT GPGOL
)
set(WARN_TOOMANY_RECIPIENTS_DEFAULT true)
set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT true)
configure_file(editor/settings/messagecomposer.kcfg.in ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer.kcfg)
kconfig_add_kcfg_files(gpgol-server-static editor/settings/messagecomposersettings.kcfgc)
install(FILES composerui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/gpgol-server)
target_sources(gpgol-server-static PUBLIC ${gpgol-server-static_SRCS})
target_link_libraries(gpgol-server-static PUBLIC
common
Qt6::HttpServer
Qt6::Widgets
Qt6::PrintSupport
KF6::CalendarCore
KF6::ConfigCore
KF6::ConfigGui
KF6::Contacts
KF6::Completion
KF6::CoreAddons
KF6::ColorScheme
KF6::Codecs
KF6::GuiAddons
KF6::SonnetUi
KF6::WidgetsAddons
KF6::XmlGui
KF6::KIOFileWidgets
KF6::Archive
KF6::TextAutoCorrectionCore
KPim6::MimeTreeParserWidgets
KPim6::Libkleo
KPim6::Libkdepim
KPim6::LdapWidgets
KPim6::PimTextEdit
KPim6::IdentityManagementCore
KPim6::IdentityManagementWidgets
)
add_executable(gpgol-server main.cpp)
target_link_libraries(gpgol-server PRIVATE gpgol-server-static)
if (BUILD_TESTING)
add_subdirectory(autotests)
endif()
diff --git a/server/editor/composerwindow.cpp b/server/editor/composerwindow.cpp
index 9177070..4599612 100644
--- a/server/editor/composerwindow.cpp
+++ b/server/editor/composerwindow.cpp
@@ -1,1589 +1,1690 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "composerwindow.h"
// Qt includes
#include <QSplitter>
#include <QApplication>
#include <QFileDialog>
#include <QUrlQuery>
#include <QClipboard>
#include <QStatusBar>
#include <QVBoxLayout>
#include <QLabel>
#include <QTimer>
#include <QFileInfo>
#include <QPushButton>
#include <QMimeData>
#include <QMenu>
#include <QInputDialog>
#include <QPainter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QCloseEvent>
// KDE includes
#include <KLineEdit>
#include <KIconUtils>
#include <KEmailAddress>
#include <KLocalizedString>
#include <KPIMTextEdit/RichTextComposerWidget>
#include <KPIMTextEdit/RichTextComposer>
#include <KPIMTextEdit/RichTextComposerControler>
#include <KPIMTextEdit/RichTextComposerActions>
#include <KPIMTextEdit/RichTextComposerImages>
#include <KPIMTextEdit/RichTextExternalComposer>
#include <TextCustomEditor/RichTextEditorWidget>
#include <KActionCollection>
#include <KMessageBox>
#include <KToolBar>
#include <KCursorSaver>
#include <Sonnet/DictionaryComboBox>
#include <MimeTreeParserWidgets/MessageViewer>
#include <MimeTreeParserWidgets/MessageViewerDialog>
#include <KMime/Message>
#include <KIdentityManagementCore/Identity>
#include <Libkleo/KeyResolverCore>
#include <Libkleo/KeyCache>
#include <Libkleo/ExpiryChecker>
#include <Libkleo/ExpiryCheckerSettings>
#include <Libkleo/KeySelectionCombo>
+#include <Libkleo/KeySelectionDialog>
// Gpgme includes
#include <QGpgME/Protocol>
#include <gpgme++/tofuinfo.h>
// App includes
#include "../identity/identitymanager.h"
#include "../identity/identitydialog.h"
#include "recipientseditor.h"
#include "nearexpirywarning.h"
-#include "cryptostateindicatorwidget.h"
#include "composerviewbase.h"
#include "richtextcomposerng.h"
#include "signaturecontroller.h"
#include "job/dndfromarkjob.h"
#include "job/saveasfilejob.h"
#include "job/inserttextfilejob.h"
#include "attachment/attachmentcontroller.h"
#include "attachment/attachmentview.h"
#include "attachment/attachmentmodel.h"
#include "kmcomposerglobalaction.h"
#include "mailtemplates.h"
#include "messagecomposersettings.h"
#include "spellcheckerconfigdialog.h"
#include "websocketclient.h"
#include "draft/draftmanager.h"
using namespace Qt::Literals::StringLiterals;
+using namespace std::chrono_literals;
namespace {
+
+inline bool containsSMIME(unsigned int f)
+{
+ return f & (Kleo::SMIMEFormat | Kleo::SMIMEOpaqueFormat);
+}
+
+inline bool containsOpenPGP(unsigned int f)
+{
+ return f & (Kleo::OpenPGPMIMEFormat | Kleo::InlineOpenPGPFormat);
+}
+
auto findSendersUid(const std::string &addrSpec, const std::vector<GpgME::UserID> &userIds)
{
return std::find_if(userIds.cbegin(), userIds.cend(), [&addrSpec](const auto &uid) {
return uid.addrSpec() == addrSpec || (uid.addrSpec().empty() && std::string(uid.email()) == addrSpec)
|| (uid.addrSpec().empty() && (!uid.email() || !*uid.email()) && uid.name() == addrSpec);
});
}
}
ComposerWindow::ComposerWindow(const QString &from, const QString &name, const QByteArray &bearerToken, QWidget *parent)
: KXmlGuiWindow(parent)
, mFrom(from)
, mMainWidget(new QWidget(this))
, mComposerBase(new MessageComposer::ComposerViewBase(this))
, mHeadersToEditorSplitter(new QSplitter(Qt::Vertical, mMainWidget))
, mHeadersArea(new QWidget(mHeadersToEditorSplitter))
, mGrid(new QGridLayout(mHeadersArea))
, mLblFrom(new QLabel(i18nc("sender address field", "From:"), mHeadersArea))
, mButtonFrom(new QPushButton(mHeadersArea))
, mRecipientEditor(new RecipientsEditor(mHeadersArea))
, mLblSubject(new QLabel(i18nc("@label:textbox Subject of email.", "Subject:"), mHeadersArea))
, mEdtSubject(new QLineEdit(mHeadersArea))
- , mCryptoStateIndicatorWidget(new CryptoStateIndicatorWidget(mHeadersArea))
, mRichTextComposer(new MessageComposer::RichTextComposerNg(this))
, mRichTextEditorWidget(new TextCustomEditor::RichTextEditorWidget(mRichTextComposer, mMainWidget))
, mNearExpiryWarning(new NearExpiryWarning(this))
, mGlobalAction(new KMComposerGlobalAction(this, this))
, mKeyCache(Kleo::KeyCache::mutableInstance())
{
+ connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this](const GpgME::KeyListResult &result) {
+ Q_UNUSED(result);
+ mRunKeyResolverTimer->start();
+ });
bool isNew = false;
mIdentity = IdentityManager::self().fromEmail(from, isNew);
mEdtFrom = new QLabel(mHeadersArea);
if (isNew) {
// fill the idenity with default fields
auto dlg = new KMail::IdentityDialog;
mIdentity.setFullName(name);
dlg->setIdentity(mIdentity);
connect(dlg, &KMail::IdentityDialog::keyListingFinished, this, [this, dlg]() {
dlg->updateIdentity(mIdentity);
IdentityManager::self().updateIdentity(mIdentity);
});
}
mComposerBase->setBearerToken(bearerToken);
mMainWidget->resize(800, 600);
setCentralWidget(mMainWidget);
setWindowTitle(i18nc("@title:window", "Composer"));
setMinimumSize(200, 200);
mHeadersToEditorSplitter->setObjectName(QStringLiteral("mHeadersToEditorSplitter"));
mHeadersToEditorSplitter->setChildrenCollapsible(false);
auto v = new QVBoxLayout(mMainWidget);
v->setContentsMargins({});
v->addWidget(mNearExpiryWarning);
v->addWidget(mHeadersToEditorSplitter);
mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding);
mHeadersToEditorSplitter->addWidget(mHeadersArea);
const QList<int> defaultSizes{0};
mHeadersToEditorSplitter->setSizes(defaultSizes);
mGrid->setColumnStretch(0, 1);
mGrid->setColumnStretch(1, 100);
mGrid->setRowStretch(3 + 1, 100);
int row = 0;
+ mRunKeyResolverTimer = new QTimer(this);
+ mRunKeyResolverTimer->setSingleShot(true);
+ mRunKeyResolverTimer->setInterval(500ms);
+ connect(mRunKeyResolverTimer, &QTimer::timeout, this, &ComposerWindow::runKeyResolver);
+
// From
mLblFrom->setObjectName(QStringLiteral("fromLineLabel"));
mLblFrom->setFixedWidth(mRecipientEditor->setFirstColumnWidth(0));
mLblFrom->setBuddy(mEdtFrom);
auto fromWrapper = new QWidget(mHeadersArea);
auto fromWrapperLayout = new QHBoxLayout(fromWrapper);
fromWrapperLayout->setContentsMargins({});
mEdtFrom->installEventFilter(this);
mEdtFrom->setText(mFrom);
mEdtFrom->setObjectName(QStringLiteral("fromLine"));
fromWrapperLayout->addWidget(mEdtFrom);
mComposerBase->setIdentity(mIdentity);
mButtonFrom->setText(i18nc("@action:button", "Configure"));
mButtonFrom->setIcon(QIcon::fromTheme(u"configure-symbolic"_s));
connect(mButtonFrom, &QPushButton::clicked, this, &ComposerWindow::slotEditIdentity);
fromWrapperLayout->addWidget(mButtonFrom);
mGrid->addWidget(mLblFrom, row, 0);
mGrid->addWidget(fromWrapper, row, 1);
row++;
// Recipients
mGrid->addWidget(mRecipientEditor, row, 0, 1, 2);
mComposerBase->setRecipientsEditor(mRecipientEditor);
+ mRecipientEditor->setCompletionMode(KCompletion::CompletionPopup);
+ connect(mRecipientEditor, &RecipientsEditor::lineAdded, this, [this](KPIM::MultiplyingLine *line) {
+ slotRecipientEditorLineAdded(qobject_cast<RecipientLineNG *>(line));
+ });
row++;
// Subject
mEdtSubject->setObjectName(u"subjectLine"_s);
mLblSubject->setObjectName(u"subjectLineLabel"_s);
mLblSubject->setBuddy(mEdtSubject);
mGrid->addWidget(mLblSubject, row, 0);
mGrid->addWidget(mEdtSubject, row, 1);
row++;
auto editorWidget = new QWidget();
auto vLayout = new QVBoxLayout(editorWidget);
vLayout->setContentsMargins({});
vLayout->setSpacing(0);
mHeadersToEditorSplitter->addWidget(editorWidget);
- // Crypto indicator
- vLayout->addWidget(mCryptoStateIndicatorWidget);
- mCryptoStateIndicatorWidget->setObjectName(QStringLiteral("CryptoIndicator"));
-
// Message widget
auto connectionLossWidget = new KMessageWidget(this);
connectionLossWidget->hide();
connectionLossWidget->setWordWrap(true);
connectionLossWidget->setPosition(KMessageWidget::Position::Header);
vLayout->addWidget(connectionLossWidget);
auto &websocketClient = WebsocketClient::self();
connect(&websocketClient, &WebsocketClient::closed, this, [connectionLossWidget](const QString &errorMessage) {
connectionLossWidget->setText(errorMessage);
connectionLossWidget->show();
});
connect(&websocketClient, &WebsocketClient::connected, this, [connectionLossWidget]() {
connectionLossWidget->hide();
});
connect(&websocketClient, &WebsocketClient::emailSentSuccessfully, this, [this](const QString &id) {
if (id == mComposerBase->mailId()) {
auto &draftManager = DraftManager::self();
draftManager.remove(draftManager.draftById(id.toUtf8()));
hide();
}
});
// Rich text editor
mRichTextComposer->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
mRichTextComposer->setProperty("_breeze_force_frame", true);
mComposerBase->setEditor(mRichTextComposer);
vLayout->addWidget(mRichTextEditorWidget);
auto attachmentModel = new MessageComposer::AttachmentModel(this);
auto attachmentView = new AttachmentView(attachmentModel, mHeadersToEditorSplitter);
attachmentView->hideIfEmpty();
connect(attachmentView, &AttachmentView::modified, this, &ComposerWindow::setModified);
auto attachmentController = new AttachmentController(attachmentModel, attachmentView, this);
mComposerBase->setAttachmentController(attachmentController);
mComposerBase->setAttachmentModel(attachmentModel);
auto signatureController = new MessageComposer::SignatureController(this);
connect(signatureController, &MessageComposer::SignatureController::enableHtml, this, &ComposerWindow::enableHtml);
signatureController->setIdentity(mIdentity);
signatureController->setEditor(mComposerBase->editor());
mComposerBase->setSignatureController(signatureController);
connect(signatureController,
&MessageComposer::SignatureController::signatureAdded,
mComposerBase->editor()->externalComposer(),
&KPIMTextEdit::RichTextExternalComposer::startExternalEditor);
setupStatusBar(attachmentView->widget());
setupActions();
setStandardToolBarMenuEnabled(true);
- updateSignatureAndEncryptionStateIndicators();
toolBar(u"mainToolBar"_s)->show();
connect(expiryChecker().get(),
&Kleo::ExpiryChecker::expiryMessage,
this,
[&](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
Q_UNUSED(isNewMessage);
if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
const auto plainMsg = msg.replace(QStringLiteral("<p>"), QStringLiteral(" "))
.replace(QStringLiteral("</p>"), QStringLiteral(" "))
.replace(QStringLiteral("<p align=center>"), QStringLiteral(" "));
mNearExpiryWarning->addInfo(plainMsg);
mNearExpiryWarning->setWarning(info == Kleo::ExpiryChecker::OwnKeyExpired);
mNearExpiryWarning->animatedShow();
}
const QList<KPIM::MultiplyingLine *> lstLines = mRecipientEditor->lines();
for (KPIM::MultiplyingLine *line : lstLines) {
auto recipient = line->data().dynamicCast<Recipient>();
if (recipient->key().primaryFingerprint() == key.primaryFingerprint()) {
auto recipientLine = qobject_cast<RecipientLineNG *>(line);
QString iconname = QStringLiteral("emblem-warning");
if (info == Kleo::ExpiryChecker::OtherKeyExpired) {
- mEncryptionState.setAcceptedSolution(false);
+ mAcceptedSolution = false;
iconname = QStringLiteral("emblem-error");
const auto showCryptoIndicator = true;
- const auto hasOverride = mEncryptionState.hasOverride();
- const auto encrypt = mEncryptionState.encrypt();
+ const auto encrypt = mEncryptAction->isChecked();
- const bool showAllIcons = showCryptoIndicator && hasOverride && encrypt;
+ const bool showAllIcons = showCryptoIndicator && encrypt;
if (!showAllIcons) {
recipientLine->setIcon(QIcon(), msg);
return;
}
}
recipientLine->setIcon(QIcon::fromTheme(iconname), msg);
return;
}
}
});
// TODO make it possible to show this
auto dictionaryComboBox = new Sonnet::DictionaryComboBox(this);
dictionaryComboBox->hide();
mComposerBase->setDictionary(dictionaryComboBox);
slotIdentityChanged();
}
void ComposerWindow::reset(const QString &fromAddress, const QString &name, const QByteArray &bearerToken)
{
mFrom = fromAddress;
bool isNew = false;
mIdentity = IdentityManager::self().fromEmail(fromAddress, isNew);
if (isNew) {
// fill the idenity with default fields
auto dlg = new KMail::IdentityDialog;
mIdentity.setFullName(name);
dlg->setIdentity(mIdentity);
connect(dlg, &KMail::IdentityDialog::keyListingFinished, this, [this, dlg]() {
dlg->updateIdentity(mIdentity);
IdentityManager::self().updateIdentity(mIdentity);
});
}
mComposerBase->setBearerToken(bearerToken);
mEdtSubject->setText(QString());
mRecipientEditor->clear();
mComposerBase->editor()->setText(QString{});
mComposerBase->attachmentController()->clear();
}
void ComposerWindow::setupActions()
{
// Save as draft
auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this);
actionCollection()->addAction(QStringLiteral("save_in_drafts"), action);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_S));
connect(action, &QAction::triggered, this, &ComposerWindow::slotSaveDraft);
// Save as file
action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this);
actionCollection()->addAction(QStringLiteral("save_as_file"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotSaveAsFile);
// Insert file
action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this);
actionCollection()->addAction(QStringLiteral("insert_file"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotInsertFile);
// Spellchecking
mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this);
actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction);
const bool spellChecking = MessageComposer::MessageComposerSettings::self()->autoSpellChecking();
mAutoSpellCheckingAction->setChecked(spellChecking);
slotAutoSpellCheckingToggled(spellChecking);
connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &ComposerWindow::slotAutoSpellCheckingToggled);
connect(mComposerBase->editor(), &TextCustomEditor::RichTextEditor::checkSpellingChanged, this, &ComposerWindow::slotAutoSpellCheckingToggled);
action = new QAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Spellchecker..."), this);
action->setIconText(i18n("Spellchecker"));
actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotSpellcheckConfig);
// Recent actions
mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this);
actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction);
connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &ComposerWindow::slotInsertRecentFile);
connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &ComposerWindow::slotRecentListFileClear);
const QStringList urls = MessageComposer::MessageComposerSettings::self()->recentUrls();
for (const QString &url : urls) {
mRecentAction->addUrl(QUrl(url));
}
// print
KStandardAction::print(this, &ComposerWindow::slotPrint, actionCollection());
KStandardAction::printPreview(this, &ComposerWindow::slotPrintPreview, actionCollection());
// Send email action
action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
actionCollection()->addAction(QStringLiteral("mail_send"), action);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return));
connect(action, &QAction::triggered, this, &ComposerWindow::slotSend);
// Toggle rich text
mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this);
mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
mMarkupAction->setIconText(i18n("Rich Text"));
mMarkupAction->setToolTip(i18n("Toggle rich text editing mode"));
actionCollection()->addAction(QStringLiteral("html"), mMarkupAction);
connect(mMarkupAction, &KToggleAction::triggered, this, &ComposerWindow::slotToggleMarkup);
mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this);
actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction);
mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap());
connect(mWordWrapAction, &KToggleAction::toggled, this, &ComposerWindow::slotWordWrapToggled);
// Encryption action
mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this);
mEncryptAction->setIconText(i18n("Encrypt"));
actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction);
- connect(&mEncryptionState, &EncryptionState::possibleEncryptChanged, mEncryptAction, &KToggleAction::setEnabled);
- connect(mEncryptAction, &KToggleAction::triggered, &mEncryptionState, &EncryptionState::toggleOverride);
+ connect(mEncryptAction, &KToggleAction::toggled, this, &ComposerWindow::slotEncryptionButtonIconUpdate);
// Signing action
mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this);
mSignAction->setIconText(i18n("Sign"));
actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction);
- connect(&mEncryptionState, &EncryptionState::possibleEncryptChanged, mEncryptAction, &KToggleAction::setEnabled);
- connect(&mEncryptionState, &EncryptionState::encryptChanged, mEncryptAction, &KToggleAction::setChecked);
connect(mSignAction, &KToggleAction::triggered, this, &ComposerWindow::slotSignToggled);
// Append signature
mAppendSignature = new QAction(i18n("Append S&ignature"), this);
actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature);
connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
// Prepend signature
mPrependSignature = new QAction(i18n("Pr&epend Signature"), this);
actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature);
connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this);
actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition);
connect(mInsertSignatureAtCursorPosition,
&QAction::triggered,
mComposerBase->signatureController(),
&MessageComposer::SignatureController::insertSignatureAtCursor);
action = new QAction(i18n("Paste as Attac&hment"), this);
actionCollection()->addAction(QStringLiteral("paste_att"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotPasteAsAttachment);
action = new QAction(i18n("Cl&ean Spaces"), this);
actionCollection()->addAction(QStringLiteral("clean_spaces"), action);
connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace);
mRichTextComposer->composerActions()->createActions(actionCollection());
KStandardAction::close(this, &ComposerWindow::close, actionCollection());
KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection());
KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection());
KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection());
KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection());
KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection());
mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection());
mFindText = KStandardAction::find(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFind, actionCollection());
mFindNextText = KStandardAction::findNext(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFindNext, actionCollection());
mReplaceText = KStandardAction::replace(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotReplace, actionCollection());
mComposerBase->attachmentController()->createActions();
createGUI(u"composerui.rc"_s);
connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &ComposerWindow::htmlToolBarVisibilityChanged);
-
- connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &ComposerWindow::slotEncryptionButtonIconUpdate);
- connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &ComposerWindow::updateSignatureAndEncryptionStateIndicators);
- connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &ComposerWindow::slotEncryptionButtonIconUpdate);
- connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &ComposerWindow::runKeyResolver);
- connect(&mEncryptionState, &EncryptionState::acceptedSolutionChanged, this, &ComposerWindow::slotEncryptionButtonIconUpdate);
}
void ComposerWindow::setupStatusBar(QWidget *w)
{
statusBar()->addWidget(w);
mStatusbarLabel = new QLabel(this);
mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
statusBar()->addPermanentWidget(mStatusbarLabel);
mCursorLineLabel = new QLabel(this);
mCursorLineLabel->setTextFormat(Qt::PlainText);
mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral(" ")));
statusBar()->addPermanentWidget(mCursorLineLabel);
mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral(" ")));
mCursorColumnLabel->setTextFormat(Qt::PlainText);
statusBar()->addPermanentWidget(mCursorColumnLabel);
connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &ComposerWindow::slotCursorPositionChanged);
slotCursorPositionChanged();
}
void ComposerWindow::reply(const KMime::Message::Ptr &originalMessage)
{
MailTemplates::reply(originalMessage, [this](const KMime::Message::Ptr &message) {
setMessage(message);
Q_EMIT initialized();
});
}
void ComposerWindow::forward(const KMime::Message::Ptr &originalMessage)
{
MailTemplates::forward(originalMessage, [this](const KMime::Message::Ptr &message) {
setMessage(message);
Q_EMIT initialized();
});
}
void ComposerWindow::setMessage(const KMime::Message::Ptr &msg)
{
mEdtSubject->setText(msg->subject()->asUnicodeString());
mComposerBase->setMessage(msg);
}
void ComposerWindow::setModified(bool isModified)
{
mIsModified = isModified;
}
bool ComposerWindow::isModified() const
{
return mIsModified;
}
void ComposerWindow::setSigning(bool sign, bool setByUser)
{
const bool wasModified = isModified();
if (setByUser) {
setModified(true);
}
if (!mSignAction->isEnabled()) {
sign = false;
}
// check if the user defined a signing key for the current identity
if (sign && !mLastIdentityHasSigningKey) {
if (setByUser) {
KMessageBox::error(this,
i18n("<qt><p>In order to be able to sign "
"this message you first have to "
"define the (OpenPGP or S/MIME) signing key "
"to use.</p>"
"<p>Please select the key to use "
"in the identity configuration.</p>"
"</qt>"),
i18nc("@title:window", "Undefined Signing Key"));
setModified(wasModified);
}
sign = false;
}
// make sure the mSignAction is in the right state
mSignAction->setChecked(sign);
- if (!setByUser) {
- updateSignatureAndEncryptionStateIndicators();
- }
// mark the attachments for (no) signing
//if (canSignEncryptAttachments()) {
// mComposerBase->attachmentModel()->setSignSelected(sign);
//}
}
std::unique_ptr<Kleo::KeyResolverCore> ComposerWindow::fillKeyResolver()
{
auto keyResolverCore = std::make_unique<Kleo::KeyResolverCore>(true, sign());
keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown);
QStringList signingKeys, encryptionKeys;
if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
if (!mIdentity.pgpSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(mIdentity.pgpSigningKey()));
}
if (!mIdentity.pgpEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(mIdentity.pgpEncryptionKey()));
}
}
if (cryptoMessageFormat() & Kleo::AnySMIME) {
if (!mIdentity.smimeSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(mIdentity.smimeSigningKey()));
}
if (!mIdentity.smimeEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(mIdentity.smimeEncryptionKey()));
}
}
keyResolverCore->setSender(mIdentity.fullEmailAddr());
keyResolverCore->setSigningKeys(signingKeys);
keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{keyResolverCore->normalizedSender(), encryptionKeys}}}});
QStringList recipients;
const auto lst = mRecipientEditor->lines();
for (auto line : lst) {
auto recipient = line->data().dynamicCast<Recipient>();
recipients.push_back(recipient->email());
}
keyResolverCore->setRecipients(recipients);
+
+ qWarning() << recipients;
+
return keyResolverCore;
}
void ComposerWindow::slotEncryptionButtonIconUpdate()
{
- const auto state = mEncryptionState.encrypt();
- const auto setByUser = mEncryptionState.override();
- const auto acceptedSolution = mEncryptionState.acceptedSolution();
+ const auto state = mEncryptAction->isChecked();
auto icon = QIcon::fromTheme(QStringLiteral("document-encrypt"));
QString tooltip;
if (state) {
tooltip = i18nc("@info:tooltip", "Encrypt");
} else {
tooltip = i18nc("@info:tooltip", "Not Encrypt");
icon = QIcon::fromTheme(QStringLiteral("document-decrypt"));
}
- if (acceptedSolution) {
+ if (mAcceptedSolution) {
auto overlay = QIcon::fromTheme(QStringLiteral("emblem-added"));
if (state) {
overlay = QIcon::fromTheme(QStringLiteral("emblem-checked"));
}
icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner);
} else {
- if (state && setByUser) {
+ if (state) {
auto overlay = QIcon::fromTheme(QStringLiteral("emblem-warning"));
icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner);
}
}
mEncryptAction->setIcon(icon);
mEncryptAction->setToolTip(tooltip);
}
void ComposerWindow::runKeyResolver()
{
auto keyResolverCore = fillKeyResolver();
auto result = keyResolverCore->resolve();
const auto lst = mRecipientEditor->lines();
if (lst.size() == 1) {
const auto line = qobject_cast<RecipientLineNG *>(lst.first());
if (line->recipientsCount() == 0) {
- mEncryptionState.setAcceptedSolution(false);
+ mAcceptedSolution = false;
return;
}
}
- mEncryptionState.setAcceptedSolution(result.flags & Kleo::KeyResolverCore::AllResolved);
+ mAcceptedSolution = result.flags & Kleo::KeyResolverCore::AllResolved;
for (auto line_ : lst) {
auto line = qobject_cast<RecipientLineNG *>(line_);
Q_ASSERT(line);
auto recipient = line->data().dynamicCast<Recipient>();
QString dummy;
QString addrSpec;
if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
addrSpec = recipient->email();
}
auto resolvedKeys = result.solution.encryptionKeys[addrSpec];
GpgME::Key key;
if (resolvedKeys.size() == 0) { // no key found for recipient
// Search for any key, also for not accepted ons, to at least give the user more info.
key = Kleo::KeyCache::instance()->findBestByMailBox(addrSpec.toUtf8().constData(), GpgME::UnknownProtocol, Kleo::KeyCache::KeyUsage::Encrypt);
key.update(); // We need tofu information for key.
recipient->setKey(key);
} else { // A key was found for recipient
key = resolvedKeys.front();
if (recipient->key().primaryFingerprint() != key.primaryFingerprint()) {
key.update(); // We need tofu information for key.
recipient->setKey(key);
}
}
- annotateRecipientEditorLineWithCrpytoInfo(line);
+ annotateRecipientEditorLineWithCryptoInfo(line);
if (!key.isNull()) {
mExpiryChecker->checkKey(key, Kleo::ExpiryChecker::EncryptionKey);
}
}
}
-void ComposerWindow::annotateRecipientEditorLineWithCrpytoInfo(RecipientLineNG *line)
+void ComposerWindow::annotateRecipientEditorLineWithCryptoInfo(RecipientLineNG *line)
{
auto recipient = line->data().dynamicCast<Recipient>();
const auto key = recipient->key();
const auto showCryptoIndicator = true;
- const auto hasOverride = mEncryptionState.hasOverride();
- const auto encrypt = mEncryptionState.encrypt();
+ const auto encrypt = mEncryptAction->isChecked();
const bool showPositiveIcons = showCryptoIndicator && encrypt;
- const bool showAllIcons = showCryptoIndicator && hasOverride && encrypt;
+ const bool showAllIcons = showCryptoIndicator && encrypt;
QString dummy;
QString addrSpec;
bool invalidEmail = false;
if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
invalidEmail = true;
addrSpec = recipient->email();
}
if (key.isNull()) {
recipient->setEncryptionAction(Kleo::Impossible);
if (showAllIcons && !invalidEmail) {
const auto icon = QIcon::fromTheme(QStringLiteral("emblem-error"));
line->setIcon(icon, i18nc("@info:tooltip", "No key found for the recipient."));
} else {
line->setIcon(QIcon());
}
line->setProperty("keyStatus", invalidEmail ? InProgress : NoKey);
return;
}
CryptoKeyState keyState = KeyOk;
if (recipient->encryptionAction() != Kleo::DoIt) {
recipient->setEncryptionAction(Kleo::DoIt);
}
QString tooltip;
const auto uids = key.userIDs();
const auto _uid = findSendersUid(addrSpec.toStdString(), uids);
GpgME::UserID uid;
if (_uid == uids.cend()) {
uid = key.userID(0);
} else {
uid = *_uid;
}
const auto trustLevel = Kleo::trustLevel(uid);
switch (trustLevel) {
case Kleo::Level0:
if (uid.tofuInfo().isNull()) {
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. It hasn't enough validity. "
"You can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
keyState = NoKey;
} else {
switch (uid.tofuInfo().validity()) {
case GpgME::TofuInfo::NoHistory:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. "
"It hasn't been used anywhere to guarantee it belongs to the stated person. "
"By using the key will be trusted more. "
"Or you can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
break;
case GpgME::TofuInfo::Conflict:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. It has conflicting TOFU data. "
"Click the icon for details.");
keyState = NoKey;
break;
case GpgME::TofuInfo::ValidityUnknown:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. It has unknown validity in TOFU data. "
"Click the icon for details.");
keyState = NoKey;
break;
default:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. The key is marked as bad. "
"Click the icon for details.");
keyState = NoKey;
}
}
break;
case Kleo::Level1:
tooltip = i18nc("@info:tooltip",
"The encryption key is only marginally trusted and hasn't been used enough time to guarantee it belongs to the stated person. "
"By using the key will be trusted more. "
"Or you can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
break;
case Kleo::Level2:
if (uid.tofuInfo().isNull()) {
tooltip = i18nc("@info:tooltip",
"The encryption key is only marginally trusted. "
"You can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
} else {
tooltip =
i18nc("@info:tooltip",
"The encryption key is only marginally trusted, but has been used enough times to be very likely controlled by the stated person. "
"By using the key will be trusted more. "
"Or you can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
}
break;
case Kleo::Level3:
tooltip = i18nc("@info:tooltip",
"The encryption key is fully trusted. You can raise the security level, by signing the key. "
"Click the icon for details.");
break;
case Kleo::Level4:
tooltip = i18nc("@info:tooltip",
"The encryption key is ultimately trusted or is signed by another ultimately trusted key. "
"Click the icon for details.");
break;
default:
Q_UNREACHABLE();
}
if (keyState == NoKey) {
- mEncryptionState.setAcceptedSolution(false);
+ mAcceptedSolution = false;
if (showAllIcons) {
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")), tooltip);
} else {
line->setIcon(QIcon());
}
} else if (trustLevel == Kleo::Level0 && encrypt) {
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")), tooltip);
} else if (showPositiveIcons) {
// Magically, the icon name maps precisely to each trust level
// line->setIcon(QIcon::fromTheme(QStringLiteral("gpg-key-trust-level-%1").arg(trustLevel)), tooltip);
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-success")), tooltip);
} else {
line->setIcon(QIcon());
}
if (line->property("keyStatus") != keyState) {
line->setProperty("keyStatus", keyState);
}
}
void ComposerWindow::slotSignToggled(bool on)
{
setSigning(on, true);
- updateSignatureAndEncryptionStateIndicators();
-}
-
-void ComposerWindow::updateSignatureAndEncryptionStateIndicators()
-{
- mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(sign(), mEncryptionState.encrypt());
}
bool ComposerWindow::sign() const
{
return mSignAction->isChecked();
}
void ComposerWindow::slotSend()
{
mComposerBase->setFrom(mFrom);
mComposerBase->setSubject(mEdtSubject->text());
if (mComposerBase->to().isEmpty()) {
if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) {
KMessageBox::information(this,
i18n("You must specify at least one receiver, "
"either in the To: field or as CC or as BCC."));
return;
} else {
const int rc = KMessageBox::questionTwoActions(this,
i18n("To: field is empty. "
"Send message anyway?"),
i18nc("@title:window", "No To: specified"),
KGuiItem(i18n("S&end as Is"), QLatin1String("mail-send")),
KGuiItem(i18n("&Specify the To field"), QLatin1String("edit-rename")));
if (rc == KMessageBox::ButtonCode::SecondaryAction) {
return;
}
}
}
if (mComposerBase->subject().isEmpty()) {
mEdtSubject->setFocus();
const int rc = KMessageBox::questionTwoActions(this,
i18n("You did not specify a subject. "
"Send message anyway?"),
i18nc("@title:window", "No Subject Specified"),
KGuiItem(i18n("S&end as Is"), QStringLiteral("mail-send")),
KGuiItem(i18n("&Specify the Subject"), QStringLiteral("edit-rename")));
if (rc == KMessageBox::ButtonCode::SecondaryAction) {
return;
}
}
KCursorSaver saver(Qt::WaitCursor);
- const bool encrypt = mEncryptionState.encrypt();
+ const bool encrypt = mEncryptAction->isChecked();
mComposerBase->setCryptoOptions(
sign(),
encrypt,
cryptoMessageFormat());
mComposerBase->send();
}
void ComposerWindow::changeCryptoAction()
{
if (!QGpgME::openpgp() && !QGpgME::smime()) {
// no crypto whatsoever
mEncryptAction->setEnabled(false);
- mEncryptionState.setPossibleEncrypt(false);
mSignAction->setEnabled(false);
setSigning(false);
} else {
- // TODO: carl
- const bool canOpenPGPSign = QGpgME::openpgp();// && !ident.pgpSigningKey().isEmpty();
- const bool canSMIMESign = QGpgME::smime(); // && !ident.smimeSigningKey().isEmpty();
-
- setSigning((canOpenPGPSign || canSMIMESign)); // && ident.pgpAutoSign());
+ setSigning(true);
+ mEncryptAction->setChecked(true);
}
}
void ComposerWindow::slotToggleMarkup()
{
htmlToolBarVisibilityChanged(mMarkupAction->isChecked());
}
void ComposerWindow::htmlToolBarVisibilityChanged(bool visible)
{
if (visible) {
enableHtml();
} else {
disableHtml(LetUserConfirm);
}
}
void ComposerWindow::enableHtml()
{
if (mForceDisableHtml) {
disableHtml(NoConfirmationNeeded);
return;
}
mRichTextComposer->activateRichText();
if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
// Use singleshot, as we we might actually be called from a slot that wanted to disable the
// toolbar (but the messagebox in disableHtml() prevented that and called us).
// The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
// signal, so wait one event loop run for that.
QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show);
}
if (!mMarkupAction->isChecked()) {
mMarkupAction->setChecked(true);
}
mRichTextComposer->composerActions()->updateActionStates();
mRichTextComposer->composerActions()->setActionsEnabled(true);
}
void ComposerWindow::disableHtml(Confirmation confirmation)
{
bool forcePlainTextMarkup = false;
if (confirmation == LetUserConfirm && mRichTextComposer->composerControler()->isFormattingUsed()) {
int choice = KMessageBox::warningTwoActionsCancel(this,
i18n("Turning HTML mode off "
"will cause the text to lose the formatting. Are you sure?"),
i18n("Lose the formatting?"),
KGuiItem(i18n("Lose Formatting")),
KGuiItem(i18n("Add Markup Plain Text")),
KStandardGuiItem::cancel(),
QStringLiteral("LoseFormattingWarning"));
switch (choice) {
case KMessageBox::Cancel:
enableHtml();
return;
case KMessageBox::ButtonCode::SecondaryAction:
forcePlainTextMarkup = true;
break;
case KMessageBox::ButtonCode::PrimaryAction:
break;
}
}
mRichTextComposer->forcePlainTextMarkup(forcePlainTextMarkup);
mRichTextComposer->switchToPlainText();
mRichTextComposer->composerActions()->setActionsEnabled(false);
if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
// See the comment in enableHtml() why we use a singleshot timer, similar situation here.
QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide);
}
if (mMarkupAction->isChecked()) {
mMarkupAction->setChecked(false);
}
}
inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
{
const int num = 30;
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
{
const int num = 14;
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
{
const int num = 14;
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
{
const int num = 14;
return Kleo::chrono::days{qMax(1, num)};
}
std::shared_ptr<Kleo::ExpiryChecker> ComposerWindow::expiryChecker()
{
if (!mExpiryChecker) {
mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
encryptKeyNearExpiryWarningThresholdInDays(),
encryptRootCertNearExpiryWarningThresholdInDays(),
encryptChainCertNearExpiryWarningThresholdInDays()}});
}
return mExpiryChecker;
}
Kleo::CryptoMessageFormat ComposerWindow::cryptoMessageFormat() const
{
return Kleo::AutoFormat;
}
void ComposerWindow::slotEditIdentity()
{
QPointer<KMail::IdentityDialog> dlg = new KMail::IdentityDialog();
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setIdentity(mIdentity);
dlg->open();
connect(dlg, &KMail::IdentityDialog::accepted, this, [dlg, this]() {
dlg->updateIdentity(mIdentity);
IdentityManager::self().updateIdentity(mIdentity);
IdentityManager::self().writeConfig();
slotIdentityChanged();
});
}
void ComposerWindow::slotIdentityChanged()
{
mComposerBase->setIdentity(mIdentity);
mLastIdentityHasSigningKey = !mIdentity.pgpSigningKey().isEmpty() || !mIdentity.smimeSigningKey().isEmpty();
mLastIdentityHasEncryptionKey = !mIdentity.pgpEncryptionKey().isEmpty() || !mIdentity.smimeEncryptionKey().isEmpty();
mComposerBase->signatureController()->setIdentity(mIdentity);
mComposerBase->editor()->setAutocorrectionLanguage(mIdentity.autocorrectionLanguage());
mComposerBase->dictionary()->setCurrentByDictionaryName(mIdentity.dictionary());
mComposerBase->editor()->setSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary());
bool bPGPEncryptionKey = !mIdentity.pgpEncryptionKey().isEmpty();
bool bPGPSigningKey = !mIdentity.pgpSigningKey().isEmpty();
bool bSMIMEEncryptionKey = !mIdentity.smimeEncryptionKey().isEmpty();
bool bSMIMESigningKey = !mIdentity.smimeSigningKey().isEmpty();
if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
if (bPGPEncryptionKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.pgpEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
bPGPEncryptionKey = false;
}
}
if (bPGPSigningKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.pgpSigningKey().constData());
if (key.isNull() || !key.canSign()) {
bPGPSigningKey = false;
}
}
} else {
bPGPEncryptionKey = false;
bPGPSigningKey = false;
}
if (cryptoMessageFormat() & Kleo::AnySMIME) {
if (bSMIMEEncryptionKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.smimeEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
bSMIMEEncryptionKey = false;
}
}
if (bSMIMESigningKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.smimeSigningKey().constData());
if (key.isNull() || !key.canSign()) {
bSMIMESigningKey = false;
}
}
} else {
bSMIMEEncryptionKey = false;
bSMIMESigningKey = false;
}
bool bNewIdentityHasSigningKey = bPGPSigningKey || bSMIMESigningKey;
bool bNewIdentityHasEncryptionKey = bPGPEncryptionKey || bSMIMEEncryptionKey;
if (!mKeyCache->initialized()) {
// We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated.
mKeyCache->startKeyListing();
connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() {
checkOwnKeyExpiry(mIdentity);
runKeyResolver();
});
} else {
checkOwnKeyExpiry(mIdentity);
}
// save the state of the sign and encrypt button
if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) {
- mLastEncryptActionState = mEncryptionState.encrypt();
+ mLastEncryptActionState = mEncryptAction->isChecked();
}
mSignAction->setEnabled(bNewIdentityHasSigningKey);
if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) {
mLastSignActionState = sign();
setSigning(false);
}
// restore the last state of the sign and encrypt button
if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) {
setSigning(mLastSignActionState);
}
mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
const KIdentityManagementCore::Signature sig = const_cast<KIdentityManagementCore::Identity &>(mIdentity).signature();
bool isEnabledSignature = sig.isEnabledSignature();
mAppendSignature->setEnabled(isEnabledSignature);
mPrependSignature->setEnabled(isEnabledSignature);
mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature);
- mEncryptionState.setPossibleEncrypt(true);
changeCryptoAction();
- mEncryptionState.unsetOverride();
- mEncryptionState.setPossibleEncrypt(mEncryptionState.possibleEncrypt() && bNewIdentityHasEncryptionKey);
- mEncryptionState.setAutoEncrypt(mIdentity.pgpAutoEncrypt());
-
- // make sure the From and BCC fields are shown if necessary
- if (mIdentity.pgpAutoEncrypt() && mKeyCache->initialized()) {
- runKeyResolver();
- }
Q_EMIT identityChanged();
}
void ComposerWindow::checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident)
{
mNearExpiryWarning->clearInfo();
mNearExpiryWarning->hide();
if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
if (!ident.pgpEncryptionKey().isEmpty()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint",
"Your selected PGP key (%1) doesn't exist in your keyring or is not suitable for encryption.",
QString::fromUtf8(ident.pgpEncryptionKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey);
}
}
if (!ident.pgpSigningKey().isEmpty()) {
if (ident.pgpSigningKey() != ident.pgpEncryptionKey()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpSigningKey().constData());
if (key.isNull() || !key.canSign()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint",
"Your selected PGP signing key (%1) doesn't exist in your keyring or is not suitable for signing.",
QString::fromUtf8(ident.pgpSigningKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey);
}
}
}
}
if (cryptoMessageFormat() & Kleo::AnySMIME) {
if (!ident.smimeEncryptionKey().isEmpty()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint",
"Your selected SMIME key (%1) doesn't exist in your keyring or is not suitable for encryption.",
QString::fromUtf8(ident.smimeEncryptionKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey);
}
}
if (!ident.smimeSigningKey().isEmpty()) {
if (ident.smimeSigningKey() != ident.smimeEncryptionKey()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeSigningKey().constData());
if (key.isNull() || !key.canSign()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint",
"Your selected SMIME signing key (%1) doesn't exist in your keyring or is not suitable for signing.",
QString::fromUtf8(ident.smimeSigningKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey);
}
}
}
}
}
void ComposerWindow::slotCursorPositionChanged()
{
// Change Line/Column info in status bar
const int line = mComposerBase->editor()->linePosition() + 1;
const int col = mComposerBase->editor()->columnNumber() + 1;
QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line);
mCursorLineLabel->setText(temp);
temp = i18n(" Column: %1 ", col);
mCursorColumnLabel->setText(temp);
// Show link target in status bar
if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) {
const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1String(" -> ")
+ mComposerBase->editor()->composerControler()->currentLinkUrl();
mStatusbarLabel->setText(text);
} else {
mStatusbarLabel->clear();
}
}
KIdentityManagementCore::Identity ComposerWindow::identity() const
{
return mIdentity;
}
QString ComposerWindow::subject() const
{
return mEdtSubject->text();
}
QString ComposerWindow::content() const
{
return mComposerBase->editor()->toCleanHtml();
}
RecipientsEditor *ComposerWindow::recipientsEditor() const
{
return mRecipientEditor;
}
void ComposerWindow::addAttachment(const QList<AttachmentInfo> &infos, bool showWarning)
{
QStringList lst;
for (const AttachmentInfo &info : infos) {
if (showWarning) {
lst.append(info.url.toDisplayString());
}
mComposerBase->addAttachment(info.url, info.comment, false);
}
if (showWarning) {
// TODO
// mAttachmentFromExternalMissing->setAttachmentNames(lst);
// mAttachmentFromExternalMissing->animatedShow();
}
}
void ComposerWindow::addAttachment(const QString &name,
KMime::Headers::contentEncoding cte,
const QString &charset,
const QByteArray &data,
const QByteArray &mimeType)
{
Q_UNUSED(cte)
mComposerBase->addAttachment(name, name, charset, data, mimeType);
}
void ComposerWindow::insertUrls(const QMimeData *source, const QList<QUrl> &urlList)
{
QStringList urlAdded;
for (const QUrl &url : urlList) {
QString urlStr;
if (url.scheme() == QLatin1String("mailto")) {
urlStr = KEmailAddress::decodeMailtoUrl(url);
} else {
urlStr = url.toDisplayString();
// Workaround #346370
if (urlStr.isEmpty()) {
urlStr = source->text();
}
}
if (!urlAdded.contains(urlStr)) {
mComposerBase->editor()->composerControler()->insertLink(urlStr);
urlAdded.append(urlStr);
}
}
}
bool ComposerWindow::insertFromMimeData(const QMimeData *source, bool forceAttachment)
{
// If this is a PNG image, either add it as an attachment or as an inline image
if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) {
const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html")));
mComposerBase->editor()->insertHtml(html);
return true;
} else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText()
&& !forceAttachment) {
mComposerBase->editor()->insertPlainText(source->text());
return true;
} else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) {
// Get the image data before showing the dialog, since that processes events which can delete
// the QMimeData object behind our back
const QByteArray imageData = source->data(QStringLiteral("image/png"));
if (imageData.isEmpty()) {
return true;
}
if (!forceAttachment) {
if (mComposerBase->editor()->textMode()
== MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
auto image = qvariant_cast<QImage>(source->imageData());
QFileInfo fi(source->text());
QMenu menu(this);
const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image"));
menu.addAction(i18n("Add as &Attachment"));
const QAction *selectedAction = menu.exec(QCursor::pos());
if (selectedAction == addAsInlineImageAction) {
// Let the textedit from kdepimlibs handle inline images
mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi);
return true;
} else if (!selectedAction) {
return true;
}
// else fall through
}
}
// Ok, when we reached this point, the user wants to add the image as an attachment.
// Ask for the filename first.
bool ok;
QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
if (!ok) {
return true;
}
attName = attName.trimmed();
if (attName.isEmpty()) {
KMessageBox::error(this, i18n("Attachment name can't be empty"), i18nc("@title:window", "Invalid Attachment Name"));
return true;
}
addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png");
return true;
} else {
auto job = new DndFromArkJob(this);
job->setComposerWindow(this);
if (job->extract(source)) {
return true;
}
}
// If this is a URL list, add those files as attachments or text
// but do not offer this if we are pasting plain text containing an url, e.g. from a browser
const QList<QUrl> urlList = source->urls();
if (!urlList.isEmpty()) {
// Search if it's message items.
bool allLocalURLs = true;
for (const QUrl &url : urlList) {
if (!url.isLocalFile()) {
allLocalURLs = false;
}
}
if (allLocalURLs || forceAttachment) {
QList<AttachmentInfo> infoList;
infoList.reserve(urlList.count());
for (const QUrl &url : urlList) {
AttachmentInfo info;
info.url = url;
infoList.append(info);
}
addAttachment(infoList, false);
} else {
QMenu p;
const int sizeUrl(urlList.size());
const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl));
const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl));
const QAction *selectedAction = p.exec(QCursor::pos());
if (selectedAction == addAsTextAction) {
insertUrls(source, urlList);
} else if (selectedAction == addAsAttachmentAction) {
QList<AttachmentInfo> infoList;
for (const QUrl &url : urlList) {
if (url.isValid()) {
AttachmentInfo info;
info.url = url;
infoList.append(info);
}
}
addAttachment(infoList, false);
}
}
return true;
}
return false;
}
void ComposerWindow::slotSaveDraft()
{
mComposerBase->autoSaveMessage();
}
void ComposerWindow::slotSaveAsFile()
{
auto job = new SaveAsFileJob(this);
job->setParentWidget(this);
job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
job->setTextDocument(mComposerBase->editor()->document());
job->start();
}
QUrl ComposerWindow::insertFile()
{
const auto fileName = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Insert File"));
return QUrl::fromUserInput(fileName);
}
void ComposerWindow::slotInsertFile()
{
const QUrl u = insertFile();
if (u.isEmpty()) {
return;
}
mRecentAction->addUrl(u);
// Prevent race condition updating list when multiple composers are open
{
QUrlQuery query(u);
QStringList urls = MessageComposer::MessageComposerSettings::self()->recentUrls();
// Prevent config file from growing without bound
// Would be nicer to get this constant from KRecentFilesAction
const int mMaxRecentFiles = 30;
while (urls.count() > mMaxRecentFiles) {
urls.removeLast();
}
urls.prepend(u.toDisplayString());
MessageComposer::MessageComposerSettings::self()->setRecentUrls(urls);
MessageComposer::MessageComposerSettings::self()->save();
}
slotInsertRecentFile(u);
}
void ComposerWindow::slotRecentListFileClear()
{
MessageComposer::MessageComposerSettings::self()->setRecentUrls({});
MessageComposer::MessageComposerSettings::self()->save();
}
void ComposerWindow::slotInsertRecentFile(const QUrl &u)
{
if (u.fileName().isEmpty()) {
return;
}
auto job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u);
job->start();
}
void ComposerWindow::slotPrint()
{
QPrinter printer;
QPrintDialog dialog(&printer, this);
dialog.setWindowTitle(i18nc("@title:window", "Print Document"));
if (dialog.exec() != QDialog::Accepted)
return;
printInternal(&printer);
}
void ComposerWindow::slotPrintPreview()
{
auto dialog = new QPrintPreviewDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(800, 750);
dialog->setWindowTitle(i18nc("@title:window", "Print Document"));
QObject::connect(dialog, &QPrintPreviewDialog::paintRequested, this, [this](QPrinter *printer) {
printInternal(printer);
});
dialog->open();
}
void ComposerWindow::printInternal(QPrinter *printer)
{
mComposerBase->setFrom(mFrom);
mComposerBase->setSubject(mEdtSubject->text());
mComposerBase->generateMessage([printer](const QList<KMime::Message::Ptr> &messages) {
if (messages.isEmpty()) {
return;
}
MimeTreeParser::Widgets::MessageViewer messageViewer;
messageViewer.setMessage(messages[0]);
QPainter painter;
painter.begin(printer);
const auto pageLayout = printer->pageLayout();
const auto pageRect = pageLayout.paintRectPixels(printer->resolution());
const double xscale = pageRect.width() / double(messageViewer.width());
const double yscale = pageRect.height() / double(messageViewer.height());
const double scale = qMin(qMin(xscale, yscale), 1.);
painter.translate(pageRect.x(), pageRect.y());
painter.scale(scale, scale);
messageViewer.print(&painter, pageRect.width());
});
}
void ComposerWindow::slotPasteAsAttachment()
{
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
if (!mimeData) {
return;
}
if (insertFromMimeData(mimeData, true)) {
return;
}
if (mimeData->hasText()) {
bool ok;
const QString attName =
QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
if (ok) {
mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
}
return;
}
}
void ComposerWindow::slotWordWrapToggled(bool on)
{
if (on) {
mComposerBase->editor()->enableWordWrap(validateLineWrapWidth());
} else {
disableWordWrap();
}
}
int ComposerWindow::validateLineWrapWidth() const
{
int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
if ((lineWrap == 0) || (lineWrap > 78)) {
lineWrap = 78;
} else if (lineWrap < 30) {
lineWrap = 30;
}
return lineWrap;
}
void ComposerWindow::disableWordWrap()
{
mComposerBase->editor()->disableWordWrap();
}
void ComposerWindow::slotAutoSpellCheckingToggled(bool enabled)
{
mAutoSpellCheckingAction->setChecked(enabled);
if (mComposerBase->editor()->checkSpellingEnabled() != enabled) {
mComposerBase->editor()->setCheckSpellingEnabled(enabled);
}
//mStatusBarLabelSpellCheckingChangeMode->setToggleMode(enabled);
}
void ComposerWindow::slotSpellcheckConfig()
{
QPointer<SpellCheckerConfigDialog> dialog = new SpellCheckerConfigDialog(this);
if (!mComposerBase->editor()->spellCheckingLanguage().isEmpty()) {
dialog->setLanguage(mComposerBase->editor()->spellCheckingLanguage());
}
if (dialog->exec()) {
mComposerBase->editor()->setSpellCheckingLanguage(dialog->language());
}
delete dialog;
}
void ComposerWindow::closeEvent(QCloseEvent *event)
{
event->ignore();
ComposerWindowFactory::self().clear(this);
}
bool ComposerWindow::queryClose()
{
return KXmlGuiWindow::queryClose();
}
+
+void ComposerWindow::slotRecipientEditorLineAdded(RecipientLineNG *line)
+{
+ connect(line, &RecipientLineNG::countChanged, this, [this, line]() {
+ slotRecipientAdded(line);
+ });
+ connect(line, &RecipientLineNG::iconClicked, this, [this, line]() {
+ slotRecipientLineIconClicked(line);
+ });
+ connect(line, &RecipientLineNG::destroyed, this, &ComposerWindow::slotRecipientEditorFocusChanged, Qt::QueuedConnection);
+ connect(line, &RecipientLineNG::activeChanged, this, [this, line]() {
+ slotRecipientFocusLost(line);
+ }, Qt::QueuedConnection);
+
+ slotRecipientEditorFocusChanged();
+}
+
+void ComposerWindow::slotRecipientLineIconClicked(RecipientLineNG *line)
+{
+ const auto recipient = line->data().dynamicCast<Recipient>();
+
+ if (!recipient->key().isNull()) {
+ const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
+ if (exec.isEmpty()
+ || !QProcess::startDetached(exec,
+ {QStringLiteral("--query"),
+ QString::fromLatin1(recipient->key().primaryFingerprint()),
+ QStringLiteral("--parent-windowid"),
+ QString::number(winId())})) {
+ qCWarning(EDITOR_LOG) << "Unable to execute kleopatra";
+ }
+ return;
+ }
+
+ const auto msg = i18nc(
+ "if in your language something like "
+ "'certificate(s)' is not possible please "
+ "use the plural in the translation",
+ "<qt>No valid and trusted encryption certificate was "
+ "found for \"%1\".<br/><br/>"
+ "Select the certificate(s) which should "
+ "be used for this recipient. If there is no suitable certificate in the list "
+ "you can also search for external certificates by clicking the button: "
+ "search for external certificates.</qt>",
+ recipient->name().isEmpty() ? recipient->email() : recipient->name());
+
+ const bool opgp = containsOpenPGP(cryptoMessageFormat());
+ const bool x509 = containsSMIME(cryptoMessageFormat());
+
+ QPointer<Kleo::KeySelectionDialog> dlg = new Kleo::KeySelectionDialog(
+ i18n("Encryption Key Selection"),
+ msg,
+ recipient->email(),
+ {},
+ Kleo::KeySelectionDialog::ValidEncryptionKeys | (opgp ? Kleo::KeySelectionDialog::OpenPGPKeys : 0) | (x509 ? Kleo::KeySelectionDialog::SMIMEKeys : 0),
+ false, // multi-selection
+ false); // "remember choice" box;
+
+ dlg->open();
+
+ connect(dlg, &QDialog::accepted, this, [dlg, recipient, line, this]() {
+ auto key = dlg->selectedKey();
+ key.update(); // We need tofu information for key.
+ recipient->setKey(key);
+ annotateRecipientEditorLineWithCryptoInfo(line);
+ });
+}
+
+void ComposerWindow::slotRecipientEditorFocusChanged()
+{
+ if (!mEncryptAction->isChecked()) {
+ return;
+ }
+
+ if (mKeyCache->initialized()) {
+ mRunKeyResolverTimer->stop();
+ runKeyResolver();
+ }
+}
+
+void ComposerWindow::slotRecipientAdded(RecipientLineNG *line)
+{
+ if (line->recipientsCount() == 0) {
+ return;
+ }
+
+ if (!mKeyCache->initialized()) {
+ if (line->property("keyLookupJob").toBool()) {
+ return;
+ }
+
+ line->setProperty("keyLookupJob", true);
+ // We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated.
+ connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this, line]() {
+ slotRecipientAdded(line);
+ });
+ return;
+ }
+
+ if (mKeyCache->initialized()) {
+ mRunKeyResolverTimer->start();
+ }
+}
+
+void ComposerWindow::slotRecipientFocusLost(RecipientLineNG *line)
+{
+ if (line->recipientsCount() == 0) {
+ return;
+ }
+
+ if (mKeyCache->initialized()) {
+ mRunKeyResolverTimer->start();
+ }
+}
diff --git a/server/editor/composerwindow.h b/server/editor/composerwindow.h
index 93ad5ef..28bceaf 100644
--- a/server/editor/composerwindow.h
+++ b/server/editor/composerwindow.h
@@ -1,224 +1,225 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// Qt includes
#include <QUrl>
// KDE includes
#include <KXmlGuiWindow>
#include <Libkleo/Enum>
#include <Libkleo/KeyCache>
#include <KMime/Headers>
#include <KMime/Message>
#include <KIdentityManagementCore/Identity>
// App includes
-#include "encryptionstate.h"
#include "composerwindowfactory.h"
class QSplitter;
class QLabel;
class QPrinter;
class QGridLayout;
class QLineEdit;
class QPushButton;
class KLineEdit;
class RecipientsEditor;
class KToggleAction;
-class CryptoStateIndicatorWidget;
class RecipientLineNG;
class NearExpiryWarning;
class IdentityCombo;
class KMComposerGlobalAction;
class KRecentFilesAction;
namespace KPIMTextEdit
{
class RichTextComposerWidget;
class RichTextComposer;
}
namespace TextCustomEditor
{
class RichTextEditorWidget;
}
namespace MessageComposer {
class ComposerViewBase;
class RichTextComposerNg;
}
namespace Kleo
{
class KeyResolverCore;
class ExpiryChecker;
}
class ComposerWindow : public KXmlGuiWindow
{
Q_OBJECT
enum Confirmation {
LetUserConfirm,
NoConfirmationNeeded,
};
public:
struct AttachmentInfo {
QString comment;
QUrl url;
};
/// The identity assigned to this message.
KIdentityManagementCore::Identity identity() const;
/// The subject of the message.
QString subject() const;
/// The recipients of the message.
RecipientsEditor *recipientsEditor() const;
/// The content of the message.
QString content() const;
void addAttachment(const QList<AttachmentInfo> &infos, bool showWarning);
void reply(const KMime::Message::Ptr &message);
void forward(const KMime::Message::Ptr &message);
void setMessage(const KMime::Message::Ptr &message);
private Q_SLOTS:
void slotSend();
void slotToggleMarkup();
void slotSignToggled(bool on);
void slotSaveDraft();
void slotSaveAsFile();
void slotInsertFile();
void slotEncryptionButtonIconUpdate();
void slotEditIdentity();
void slotIdentityChanged();
void slotPrint();
void slotPrintPreview();
void slotWordWrapToggled(bool on);
void slotAutoSpellCheckingToggled(bool enabled);
void slotSpellcheckConfig();
void printInternal(QPrinter *printer);
void enableHtml();
void slotPasteAsAttachment();
void disableHtml(Confirmation confirmation);
void slotCursorPositionChanged();
void slotInsertRecentFile(const QUrl &u);
void slotRecentListFileClear();
+ void slotRecipientEditorFocusChanged();
+ void slotRecipientAdded(RecipientLineNG *line);
+ void slotRecipientFocusLost(RecipientLineNG *line);
+ void slotRecipientEditorLineAdded(RecipientLineNG *line);
+ void slotRecipientLineIconClicked(RecipientLineNG *line);
void insertUrls(const QMimeData *source, const QList<QUrl> &urlList);
bool insertFromMimeData(const QMimeData *source, bool forceAttachment);
QUrl insertFile();
void addAttachment(const QString &name, KMime::Headers::contentEncoding cte, const QString &charset, const QByteArray &data, const QByteArray &mimeType);
/// Set whether the message will be signed.
void setSigning(bool sign, bool setByUser = false);
/// Set whether the message should be treated as modified or not.
void setModified(bool modified);
std::shared_ptr<Kleo::ExpiryChecker> expiryChecker();
Q_SIGNALS:
void identityChanged();
void initialized();
protected:
friend ComposerWindowFactory;
explicit ComposerWindow(const QString &fromAddress, const QString &name, const QByteArray &bearerToken, QWidget *parent = nullptr);
void reset(const QString &fromAddress, const QString &name, const QByteArray &bearerToken);
void closeEvent(QCloseEvent *event) override;
private:
enum CryptoKeyState {
NoState = 0,
InProgress,
KeyOk,
NoKey,
};
/// Ask for confirmation if the message was changed.
[[nodiscard]] bool queryClose() override;
- void annotateRecipientEditorLineWithCrpytoInfo(RecipientLineNG *line);
+ void annotateRecipientEditorLineWithCryptoInfo(RecipientLineNG *line);
void setupActions();
void setupStatusBar(QWidget *w);
void htmlToolBarVisibilityChanged(bool visible);
void changeCryptoAction();
void runKeyResolver();
int validateLineWrapWidth() const;
void disableWordWrap();
void checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident);
std::unique_ptr<Kleo::KeyResolverCore> fillKeyResolver();
/**
* Returns true if the message was modified by the user.
*/
[[nodiscard]] bool isModified() const;
/**
* Returns true if the message will be signed.
*/
[[nodiscard]] bool sign() const;
Kleo::CryptoMessageFormat cryptoMessageFormat() const;
- void updateSignatureAndEncryptionStateIndicators();
-
KIdentityManagementCore::Identity mIdentity;
QString mFrom;
QWidget *const mMainWidget;
// splitter between the headers area and the actual editor
MessageComposer::ComposerViewBase *const mComposerBase;
QSplitter *const mHeadersToEditorSplitter;
QWidget *const mHeadersArea;
QGridLayout *const mGrid;
QLabel *const mLblFrom;
QPushButton *const mButtonFrom;
RecipientsEditor *const mRecipientEditor;
QLabel *const mLblSubject;
QLineEdit *const mEdtSubject;
- CryptoStateIndicatorWidget *const mCryptoStateIndicatorWidget;
MessageComposer::RichTextComposerNg *const mRichTextComposer;
TextCustomEditor::RichTextEditorWidget *const mRichTextEditorWidget;
NearExpiryWarning *const mNearExpiryWarning;
KMComposerGlobalAction *const mGlobalAction;
QLabel *mEdtFrom = nullptr;
bool mForceDisableHtml = false;
bool mLastIdentityHasSigningKey = false;
bool mLastIdentityHasEncryptionKey = false;
QAction *mEncryptAction = nullptr;
QAction *mSignAction = nullptr;
QAction *mAppendSignature = nullptr;
QAction *mPrependSignature = nullptr;
QAction *mInsertSignatureAtCursorPosition = nullptr;
QAction *mSelectAll = nullptr;
QAction *mFindText = nullptr;
QAction *mFindNextText = nullptr;
QAction *mReplaceText = nullptr;
QLabel *mStatusbarLabel = nullptr;
QLabel *mCursorLineLabel = nullptr;
QLabel *mCursorColumnLabel = nullptr;
- EncryptionState mEncryptionState;
KToggleAction *mMarkupAction = nullptr;
std::shared_ptr<Kleo::ExpiryChecker> mExpiryChecker;
bool mIsModified = false;
+ bool mAcceptedSolution = false;
KRecentFilesAction *mRecentAction = nullptr;
KToggleAction *mWordWrapAction = nullptr;
KToggleAction *mAutoSpellCheckingAction = nullptr;
bool mLastSignActionState = false;
std::shared_ptr<Kleo::KeyCache> mKeyCache;
bool mLastEncryptActionState = false;
+ QTimer *mRunKeyResolverTimer = nullptr;
};
diff --git a/server/editor/encryptionstate.cpp b/server/editor/encryptionstate.cpp
deleted file mode 100644
index 8708b42..0000000
--- a/server/editor/encryptionstate.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- SPDX-FileCopyrightText: 2022 Sandro Knauß <sknauss@kde.org>
- SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-*/
-
-#include "encryptionstate.h"
-
-EncryptionState::EncryptionState()
-{
- connect(this, &EncryptionState::acceptedSolutionChanged, this, &EncryptionState::updateEncrypt);
- connect(this, &EncryptionState::overrideChanged, this, &EncryptionState::updateEncrypt);
- connect(this, &EncryptionState::possibleEncryptChanged, this, &EncryptionState::updateEncrypt);
- connect(this, &EncryptionState::autoEncryptChanged, this, &EncryptionState::updateEncrypt);
-}
-
-bool EncryptionState::override() const
-{
- return m_override;
-}
-
-void EncryptionState::setOverride(bool setByUser)
-{
- if (!m_hasOverride) {
- m_hasOverride = true;
- Q_EMIT hasOverrideChanged(true);
- // Before the override setting was undefined, we should trigger a signal with correct state
- m_override = setByUser;
- Q_EMIT overrideChanged(setByUser);
- return;
- }
-
- if (m_override == setByUser) {
- return;
- }
-
- m_override = setByUser;
- Q_EMIT overrideChanged(m_override);
-}
-
-void EncryptionState::toggleOverride()
-{
- setOverride(!encrypt());
-}
-
-void EncryptionState::unsetOverride()
-{
- if (!m_hasOverride) {
- return;
- }
- m_hasOverride = false;
- Q_EMIT hasOverrideChanged(false);
- Q_EMIT overrideChanged(false);
-}
-
-bool EncryptionState::hasOverride() const
-{
- return m_hasOverride;
-}
-
-bool EncryptionState::acceptedSolution() const
-{
- return m_acceptedSolution;
-}
-
-void EncryptionState::setAcceptedSolution(bool acceptedSolution)
-{
- if (m_acceptedSolution == acceptedSolution) {
- return;
- }
-
- m_acceptedSolution = acceptedSolution;
- Q_EMIT acceptedSolutionChanged(m_acceptedSolution);
-}
-
-bool EncryptionState::possibleEncrypt() const
-{
- return m_possibleEncrypt;
-}
-
-void EncryptionState::setPossibleEncrypt(bool possibleEncrypt)
-{
- if (m_possibleEncrypt == possibleEncrypt) {
- return;
- }
-
- m_possibleEncrypt = possibleEncrypt;
- Q_EMIT possibleEncryptChanged(m_possibleEncrypt);
-}
-
-bool EncryptionState::autoEncrypt() const
-{
- return m_autoEncrypt;
-}
-
-void EncryptionState::setAutoEncrypt(bool autoEncrypt)
-{
- if (m_autoEncrypt == autoEncrypt) {
- return;
- }
-
- m_autoEncrypt = autoEncrypt;
- Q_EMIT autoEncryptChanged(m_autoEncrypt);
-}
-
-void EncryptionState::setEncrypt(bool encrypt)
-{
- if (m_encrypt == encrypt) {
- return;
- }
-
- m_encrypt = encrypt;
- Q_EMIT encryptChanged(m_encrypt);
-}
-
-void EncryptionState::updateEncrypt()
-{
- if (m_hasOverride) {
- setEncrypt(m_override);
- return;
- }
- if (!m_possibleEncrypt) {
- setEncrypt(false);
- return;
- }
-
- if (!m_autoEncrypt) {
- setEncrypt(false);
- return;
- }
-
- setEncrypt(m_acceptedSolution);
-}
-
-bool EncryptionState::encrypt() const
-{
- return m_encrypt;
-}
diff --git a/server/editor/encryptionstate.h b/server/editor/encryptionstate.h
deleted file mode 100644
index bdea786..0000000
--- a/server/editor/encryptionstate.h
+++ /dev/null
@@ -1,129 +0,0 @@
-// SPDX-FileCopyrightText: 2022 Sandro Knauß <sknauss@kde.org>
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-
-/// Encryption state
-///
-/// This store whether the message will be encrypted or not.
-class EncryptionState : public QObject
-{
- Q_OBJECT
- Q_PROPERTY(bool override READ override WRITE setOverride NOTIFY overrideChanged)
- Q_PROPERTY(bool possibleEncrypt READ possibleEncrypt WRITE setPossibleEncrypt NOTIFY possibleEncryptChanged)
-
- /// This property holds whether the user want auto encryption.
- /// \see autoEncrypt
- /// \see setAutoEncrypt
- /// \see autoEncryptChanged
- Q_PROPERTY(bool autoEncrypt READ autoEncrypt WRITE setAutoEncrypt NOTIFY autoEncryptChanged)
-
- /// This property holds whether keys were found for all the recipients.
- /// \see acceptedSolution
- /// \see setAcceptedSolution
- /// \see acceptedSolutionChanged
- Q_PROPERTY(bool acceptedSolution READ acceptedSolution WRITE setAcceptedSolution NOTIFY acceptedSolutionChanged)
-
- /// This property holds whether we encrypt the message.
- /// \see encrypt
- /// \see encryptChanged
- Q_PROPERTY(bool encrypt READ encrypt NOTIFY encryptChanged)
-
-public:
- /**
- * Default constructor
- */
- EncryptionState();
-
- /**
- * @return the user set the encryption state no matter what
- */
- [[nodiscard]] bool override() const;
-
- /**
- * @return true when set an override
- */
- [[nodiscard]] bool hasOverride() const;
-
- /**
- * @return we have encryption keys for the user so in principal it is possible to encrypt
- */
- [[nodiscard]] bool possibleEncrypt() const;
-
- [[nodiscard]] bool autoEncrypt() const;
-
- /**
- * @return we found a set of keys to encrypt to everyone
- */
- [[nodiscard]] bool acceptedSolution() const;
-
- /**
- * @return the encrypt
- */
- [[nodiscard]] bool encrypt() const;
-
-public Q_SLOTS:
- /**
- * Sets the override.
- *
- * @param override the new override
- */
- void setOverride(bool override);
-
- /**
- * Delete the override.
- */
- void unsetOverride();
-
- /**
- * Toggles the override
- */
- void toggleOverride();
-
- /**
- * Sets the acceptedSolution.
- *
- * @param acceptedSolution the new acceptedSolution
- */
- void setAcceptedSolution(bool acceptedSolution);
-
- /**
- * Sets the possibleEncrypt.
- *
- * @param possibleEncrypt the new possibleEncrypt
- */
- void setPossibleEncrypt(bool possibleEncrypt);
-
- /**
- * Sets the autoEncrypt.
- *
- * @param autoEncrypt the new autoEncrypt
- */
- void setAutoEncrypt(bool autoEncrypt);
-
-Q_SIGNALS:
- void overrideChanged(bool override);
- void hasOverrideChanged(bool hasOverride);
-
- void acceptedSolutionChanged(bool acceptedSolution);
-
- void possibleEncryptChanged(bool possibleEncrypt);
-
- void autoEncryptChanged(bool autoEncrypt);
-
- void encryptChanged(bool encrypt);
-
-private:
- void setEncrypt(bool encrypt);
- void updateEncrypt();
-
-private:
- bool m_override = false;
- bool m_hasOverride = false;
- bool m_acceptedSolution = false;
- bool m_possibleEncrypt = false;
- bool m_autoEncrypt = false;
- bool m_encrypt = false;
-};
diff --git a/server/main.cpp b/server/main.cpp
index ee12c5c..2663051 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -1,46 +1,50 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "controllers/emailcontroller.h"
#include <QHttpServer>
#include <QHttpServerResponse>
#include <QApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
#include <QTimer>
#include <QNetworkReply>
#include <KLocalizedString>
+#include <Libkleo/KeyCache>
#include "websocketclient.h"
#include "webserver.h"
#include "qnam.h"
using namespace Qt::Literals::StringLiterals;
using namespace std::chrono;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("gpgol"));
QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &) {
reply->ignoreSslErrors();
});
WebServer server;
server.run();
if (!server.running()) {
qWarning() << "Server failed to listen on a port.";
return 1;
}
const auto port = server.port();
WebsocketClient::self(QUrl(u"wss://localhost:5657/"_s), port);
+ auto keyCache = Kleo::KeyCache::mutableInstance();
+ keyCache->startKeyListing();
+
return app.exec();
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jan 1, 2:33 AM (10 h, 34 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
15/0c/e01b683e9bce90fdfa9286ad656b
Attached To
rOJ GpgOL.js
Event Timeline
Log In to Comment