Page MenuHome GnuPG

No OneTemporary

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

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

Event Timeline