diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 987f39b..5e3ddc2 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,272 +1,272 @@ # SPDX-FileCopyrightText: 2023 g10 code GmbH # SPDX-Contributor: Carl Schwan # SPDX-License-Identifier: BSD-2-Clause add_library(gpgol-server-static) target_sources(gpgol-server-static PUBLIC - composerwindowfactory.cpp - composerwindowfactory.h websocketclient.cpp websocketclient.h # Identity identity/addressvalidationjob.cpp identity/addressvalidationjob.h identity/identitycombo.cpp identity/identitycombo.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/composer.cpp editor/composer.h editor/composerviewbase.cpp editor/composerviewbase.h - editor/composerdialog.cpp - editor/composerdialog.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/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 ) 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_SRCS 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 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) diff --git a/server/controllers/emailcontroller.cpp b/server/controllers/emailcontroller.cpp index 68fd94c..4f5f77b 100644 --- a/server/controllers/emailcontroller.cpp +++ b/server/controllers/emailcontroller.cpp @@ -1,193 +1,193 @@ // SPDX-FileCopyrightText: 2023 g10 code GmbH // SPDX-Contributor: Carl Schwan // SPDX-License-Identifier: GPL-2.0-or-later #include "emailcontroller.h" #include #include #include #include #include #include #include #include #include -#include "../editor/composerdialog.h" -#include "../draft/draftmanager.h" -#include "composerwindowfactory.h" +#include "editor/composerwindow.h" +#include "editor/composerwindowfactory.h" +#include "draft/draftmanager.h" using namespace Qt::Literals::StringLiterals; namespace { QByteArray findHeader(QList> headers, const QByteArray &key) { const auto it = std::find_if(std::cbegin(headers), std::cend(headers), [&key](auto header) { return header.first == key; }); if (it == std::cend(headers)) { return {}; } return it->second; } } QHttpServerResponse EmailController::viewEmailAction(const QHttpServerRequest &request) { const auto email = QString::fromUtf8(findHeader(request.headers(), "X-EMAIL")); const auto displayName = QString::fromUtf8(findHeader(request.headers(), "X-NAME")); const auto bearerToken = findHeader(request.headers(), "X-TOKEN"); const auto content = request.body(); KMime::Message::Ptr message(new KMime::Message()); message->setContent(KMime::CRLFtoLF(content)); message->parse(); auto dialog = new MimeTreeParser::Widgets::MessageViewerDialog({ message }); dialog->setAttribute(Qt::WA_DeleteOnClose); auto toolBar = dialog->toolBar(); toolBar->show(); // spacer QWidget* spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); toolBar->addWidget(spacer); // reply auto replyAction = new QAction(QIcon::fromTheme(u"mail-reply-sender-symbolic"_s), i18nc("@action:button", "Reply"), toolBar); QObject::connect(replyAction, &QAction::triggered, dialog, [message, email, displayName, bearerToken](bool) { auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken); dialog->reply(message); dialog->show(); }); toolBar->addAction(replyAction); auto widget = qobject_cast(toolBar->widgetForAction(replyAction)); widget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); // forward auto forwardAction = new QAction(QIcon::fromTheme(u"mail-forward-symbolic"_s), i18nc("@action:button", "Forward"), toolBar); QObject::connect(forwardAction, &QAction::triggered, dialog, [message, email, displayName, bearerToken](bool) { auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken); dialog->reply(message); dialog->show(); }); toolBar->addAction(forwardAction); widget = qobject_cast(toolBar->widgetForAction(forwardAction)); widget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); dialog->show(); return QHttpServerResponse(QJsonObject { { "status"_L1, "200"_L1 } }); } QHttpServerResponse EmailController::infoEmailAction(const QHttpServerRequest &request) { qDebug() << "request received"; const auto content = request.body(); KMime::Message::Ptr message(new KMime::Message()); message->setContent(KMime::CRLFtoLF(content)); message->parse(); MimeTreeParser::ObjectTreeParser treeParser; treeParser.parseObjectTree(message.get()); return QHttpServerResponse(QJsonObject { { "status"_L1, "200"_L1 }, { "encrypted"_L1, treeParser.hasEncryptedParts() }, { "signed"_L1, treeParser.hasSignedParts() }, { "drafts"_L1, DraftManager::self().toJson() }, }); } QHttpServerResponse EmailController::newEmailAction(const QHttpServerRequest &request) { const auto email = QString::fromUtf8(findHeader(request.headers(), "X-EMAIL")); const auto displayName = QString::fromUtf8(findHeader(request.headers(), "X-NAME")); const auto bearerToken = findHeader(request.headers(), "X-TOKEN"); auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken); dialog->show(); return QHttpServerResponse(QJsonObject { { "status"_L1, "200"_L1 } }); } QHttpServerResponse EmailController::replyEmailAction(const QHttpServerRequest &request) { const auto email = QString::fromUtf8(findHeader(request.headers(), "X-EMAIL")); const auto displayName = QString::fromUtf8(findHeader(request.headers(), "X-NAME")); const auto bearerToken = findHeader(request.headers(), "X-TOKEN"); const auto content = request.body(); KMime::Message::Ptr message(new KMime::Message()); message->setContent(KMime::CRLFtoLF(content)); message->parse(); auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken); dialog->reply(message); dialog->show(); return QHttpServerResponse(QJsonObject { { "status"_L1, "200"_L1 } }); } QHttpServerResponse EmailController::forwardEmailAction(const QHttpServerRequest &request) { const auto email = QString::fromUtf8(findHeader(request.headers(), "X-EMAIL")); const auto displayName = QString::fromUtf8(findHeader(request.headers(), "X-NAME")); const auto bearerToken = findHeader(request.headers(), "X-TOKEN"); const auto content = request.body(); KMime::Message::Ptr message(new KMime::Message()); message->setContent(KMime::CRLFtoLF(content)); message->parse(); auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken); dialog->forward(message); dialog->show(); return QHttpServerResponse(QJsonObject { { "status"_L1, "200"_L1 } }); } QHttpServerResponse EmailController::draftAction(QString draftId, const QHttpServerRequest &request) { const auto email = QString::fromUtf8(findHeader(request.headers(), "X-EMAIL")); const auto displayName = QString::fromUtf8(findHeader(request.headers(), "X-NAME")); const auto bearerToken = findHeader(request.headers(), "X-TOKEN"); const auto draft = DraftManager::self().draftById(draftId.toUtf8()); if (!draft.isValid()) { return QHttpServerResponse(QJsonObject { { "status"_L1, "404"_L1 } }, QHttpServerResponse::StatusCode::NotFound); } if (request.method() == QHttpServerRequest::Method::Post) { // POST auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken); dialog->setMessage(draft.mime()); dialog->show(); } else { // DELETE if (!DraftManager::self().remove(draft)) { return QHttpServerResponse(QJsonObject { { "status"_L1, "500"_L1 } }, QHttpServerResponse::StatusCode::InternalServerError); } } return QHttpServerResponse(QJsonObject { { "status"_L1, "200"_L1 } }); } diff --git a/server/editor/attachment/attachmentcontroller.cpp b/server/editor/attachment/attachmentcontroller.cpp index 61e38cb..3089c08 100644 --- a/server/editor/attachment/attachmentcontroller.cpp +++ b/server/editor/attachment/attachmentcontroller.cpp @@ -1,130 +1,130 @@ /* * This file is part of KMail. * SPDX-FileCopyrightText: 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "attachmentcontroller.h" #include "attachmentview.h" #include "attachmentmodel.h" #include "attachmentpart.h" #include "attachmentview.h" -#include "editor/composerdialog.h" +#include "editor/composerwindow.h" #include "editor_debug.h" #include #include #include #include #include #include using namespace MessageCore; -AttachmentController::AttachmentController(MessageComposer::AttachmentModel *model, AttachmentView *view, ComposerDialog *composer) +AttachmentController::AttachmentController(MessageComposer::AttachmentModel *model, AttachmentView *view, ComposerWindow *composer) : AttachmentControllerBase(model, composer, composer->actionCollection()) , mComposer(composer) , mView(view) { - connect(composer, &ComposerDialog::identityChanged, this, &AttachmentController::identityChanged); + connect(composer, &ComposerWindow::identityChanged, this, &AttachmentController::identityChanged); connect(view, &AttachmentView::contextMenuRequested, this, &AttachmentControllerBase::showContextMenu); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AttachmentController::selectionChanged); connect(view, &QAbstractItemView::doubleClicked, this, &AttachmentController::doubleClicked); connect(this, &AttachmentController::refreshSelection, this, &AttachmentController::selectionChanged); connect(this, &AttachmentController::showAttachment, this, &AttachmentController::onShowAttachment); connect(this, &AttachmentController::selectedAllAttachment, this, &AttachmentController::slotSelectAllAttachment); connect(this, &AttachmentController::actionsCreated, this, &AttachmentController::slotActionsCreated); } AttachmentController::~AttachmentController() = default; void AttachmentController::slotSelectAllAttachment() { mView->selectAll(); } void AttachmentController::identityChanged() { const KIdentityManagementCore::Identity &identity = mComposer->identity(); // "Attach public key" is only possible if OpenPGP support is available: enableAttachPublicKey(QGpgME::openpgp()); // "Attach my public key" is only possible if OpenPGP support is // available and the user specified his key for the current identity: enableAttachMyPublicKey(QGpgME::openpgp() && !identity.pgpEncryptionKey().isEmpty()); } void AttachmentController::attachMyPublicKey() { const KIdentityManagementCore::Identity &identity = mComposer->identity(); qCDebug(EDITOR_LOG) << identity.identityName(); auto keyCache = Kleo::KeyCache::instance(); auto key = keyCache->findByFingerprint(identity.pgpEncryptionKey().data()); exportPublicKey(key); } void AttachmentController::slotActionsCreated() { // Disable public key actions if appropriate. identityChanged(); // Disable actions like 'Remove', since nothing is currently selected. selectionChanged(); } void AttachmentController::selectionChanged() { const QModelIndexList selectedRows = mView->selectionModel()->selectedRows(); AttachmentPart::List selectedParts; selectedParts.reserve(selectedRows.count()); for (const QModelIndex &index : selectedRows) { auto part = mView->model()->data(index, MessageComposer::AttachmentModel::AttachmentPartRole).value(); selectedParts.append(part); } setSelectedParts(selectedParts); } void AttachmentController::onShowAttachment(KMime::Content *content, const QByteArray &charset) { const QString charsetStr = QString::fromLatin1(charset); if (content->bodyAsMessage()) { KMime::Message::Ptr message(new KMime::Message); message->setContent(content->bodyAsMessage()->encodedContent()); message->parse(); auto dialog = new MimeTreeParser::Widgets::MessageViewerDialog({ message }); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } } void AttachmentController::doubleClicked(const QModelIndex &itemClicked) { if (!itemClicked.isValid()) { qCDebug(EDITOR_LOG) << "Received an invalid item clicked index"; return; } // The itemClicked index will contain the column information. But we want to retrieve // the AttachmentPart, so we must recreate the QModelIndex without the column information const QModelIndex &properItemClickedIndex = mView->model()->index(itemClicked.row(), 0); auto part = mView->model()->data(properItemClickedIndex, MessageComposer::AttachmentModel::AttachmentPartRole).value(); // We can't edit encapsulated messages, but we can view them. if (part->isMessageOrMessageCollection()) { viewAttachment(part); } else { editAttachment(part); } } #include "moc_attachmentcontroller.cpp" diff --git a/server/editor/attachment/attachmentcontroller.h b/server/editor/attachment/attachmentcontroller.h index 66ebf85..c4f41d0 100644 --- a/server/editor/attachment/attachmentcontroller.h +++ b/server/editor/attachment/attachmentcontroller.h @@ -1,46 +1,46 @@ /* * This file is part of KMail. * SPDX-FileCopyrightText: 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "attachmentcontrollerbase.h" class QModelIndex; -class ComposerDialog; +class ComposerWindow; namespace MessageComposer { class AttachmentModel; } class AttachmentView; class AttachmentController : public MessageComposer::AttachmentControllerBase { Q_OBJECT public: - explicit AttachmentController(MessageComposer::AttachmentModel *model, AttachmentView *view, ComposerDialog *composer); + explicit AttachmentController(MessageComposer::AttachmentModel *model, AttachmentView *view, ComposerWindow *composer); ~AttachmentController() override; public Q_SLOTS: void attachMyPublicKey() override; private: void identityChanged(); void slotActionsCreated(); void selectionChanged(); void onShowAttachment(KMime::Content *content, const QByteArray &charset); void doubleClicked(const QModelIndex &itemClicked); void slotSelectAllAttachment(); - ComposerDialog *const mComposer; + ComposerWindow *const mComposer; AttachmentView *const mView; }; diff --git a/server/editor/composerdialog.cpp b/server/editor/composerwindow.cpp similarity index 93% rename from server/editor/composerdialog.cpp rename to server/editor/composerwindow.cpp index 26ff0b9..60d1031 100644 --- a/server/editor/composerdialog.cpp +++ b/server/editor/composerwindow.cpp @@ -1,1570 +1,1570 @@ // SPDX-FileCopyrightText: 2023 g10 code Gmbh // SPDX-Contributor: Carl Schwan // SPDX-License-Identifier: GPL-2.0-or-later -#include "composerdialog.h" +#include "composerwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Gpgme includes #include #include // App includes #include "../identity/identitymanager.h" #include "../identity/identitycombo.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; namespace { auto findSendersUid(const std::string &addrSpec, const std::vector &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); }); } } -ComposerDialog::ComposerDialog(const QString &from, const QString &name, const QByteArray &bearerToken, QWidget *parent) +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()) { bool isNew = false; mIdentity = IdentityManager::self().fromEmail(from, isNew); mEdtFrom = new IdentityCombo(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 defaultSizes{0}; mHeadersToEditorSplitter->setSizes(defaultSizes); mGrid->setColumnStretch(0, 1); mGrid->setColumnStretch(1, 100); mGrid->setRowStretch(3 + 1, 100); int row = 0; // 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->setCurrentIdentity(mFrom); mEdtFrom->setObjectName(QStringLiteral("fromLine")); - connect(mEdtFrom, &QComboBox::currentIndexChanged, this, &ComposerDialog::slotIdentityChanged); + connect(mEdtFrom, &QComboBox::currentIndexChanged, this, &ComposerWindow::slotIdentityChanged); fromWrapperLayout->addWidget(mEdtFrom); mComposerBase->setIdentityCombo(mEdtFrom); mComposerBase->setIdentityManager(&IdentityManager::self()); mButtonFrom->setText(i18nc("@action:button", "Configure")); mButtonFrom->setIcon(QIcon::fromTheme(u"configure-symbolic"_s)); - connect(mButtonFrom, &QPushButton::clicked, this, &ComposerDialog::slotEditIdentity); + 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); 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, &ComposerDialog::setModified); + 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, &ComposerDialog::enableHtml); + 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("

"), QStringLiteral(" ")) .replace(QStringLiteral("

"), QStringLiteral(" ")) .replace(QStringLiteral("

"), QStringLiteral(" ")); mNearExpiryWarning->addInfo(plainMsg); mNearExpiryWarning->setWarning(info == Kleo::ExpiryChecker::OwnKeyExpired); mNearExpiryWarning->animatedShow(); } const QList lstLines = mRecipientEditor->lines(); for (KPIM::MultiplyingLine *line : lstLines) { auto recipient = line->data().dynamicCast(); if (recipient->key().primaryFingerprint() == key.primaryFingerprint()) { auto recipientLine = qobject_cast(line); QString iconname = QStringLiteral("emblem-warning"); if (info == Kleo::ExpiryChecker::OtherKeyExpired) { mEncryptionState.setAcceptedSolution(false); iconname = QStringLiteral("emblem-error"); const auto showCryptoIndicator = true; const auto hasOverride = mEncryptionState.hasOverride(); const auto encrypt = mEncryptionState.encrypt(); const bool showAllIcons = showCryptoIndicator && hasOverride && 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 ComposerDialog::reset(const QString &fromAddress, const QString &name, const QByteArray &bearerToken) +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 ComposerDialog::setupActions() +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, &ComposerDialog::slotSaveDraft); + 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, &ComposerDialog::slotSaveAsFile); + 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, &ComposerDialog::slotInsertFile); + 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, &ComposerDialog::slotAutoSpellCheckingToggled); - connect(mComposerBase->editor(), &TextCustomEditor::RichTextEditor::checkSpellingChanged, this, &ComposerDialog::slotAutoSpellCheckingToggled); + 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, &ComposerDialog::slotSpellcheckConfig); + 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, &ComposerDialog::slotInsertRecentFile); - connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &ComposerDialog::slotRecentListFileClear); + 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, &ComposerDialog::slotPrint, actionCollection()); - KStandardAction::printPreview(this, &ComposerDialog::slotPrintPreview, actionCollection()); + 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, &ComposerDialog::slotSend); + 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, &ComposerDialog::slotToggleMarkup); + 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, &ComposerDialog::slotWordWrapToggled); + 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); // 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, &ComposerDialog::slotSignToggled); + 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, &ComposerDialog::slotPasteAsAttachment); + 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, &ComposerDialog::close, 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, &ComposerDialog::htmlToolBarVisibilityChanged); + connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &ComposerWindow::htmlToolBarVisibilityChanged); - connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &ComposerDialog::slotEncryptionButtonIconUpdate); - connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &ComposerDialog::updateSignatureAndEncryptionStateIndicators); - connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &ComposerDialog::slotEncryptionButtonIconUpdate); - connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &ComposerDialog::runKeyResolver); - connect(&mEncryptionState, &EncryptionState::acceptedSolutionChanged, this, &ComposerDialog::slotEncryptionButtonIconUpdate); + 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 ComposerDialog::setupStatusBar(QWidget *w) +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, &ComposerDialog::slotCursorPositionChanged); + connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &ComposerWindow::slotCursorPositionChanged); slotCursorPositionChanged(); } -void ComposerDialog::reply(const KMime::Message::Ptr &originalMessage) +void ComposerWindow::reply(const KMime::Message::Ptr &originalMessage) { MailTemplates::reply(originalMessage, [this](const KMime::Message::Ptr &message) { setMessage(message); }); } -void ComposerDialog::forward(const KMime::Message::Ptr &originalMessage) +void ComposerWindow::forward(const KMime::Message::Ptr &originalMessage) { MailTemplates::forward(originalMessage, [this](const KMime::Message::Ptr &message) { setMessage(message); }); } -void ComposerDialog::setMessage(const KMime::Message::Ptr &msg) +void ComposerWindow::setMessage(const KMime::Message::Ptr &msg) { mComposerBase->setMessage(msg, true); } -void ComposerDialog::setModified(bool isModified) +void ComposerWindow::setModified(bool isModified) { mIsModified = isModified; } -bool ComposerDialog::isModified() const +bool ComposerWindow::isModified() const { return mIsModified; } -void ComposerDialog::setSigning(bool sign, bool setByUser) +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("

In order to be able to sign " "this message you first have to " "define the (OpenPGP or S/MIME) signing key " "to use.

" "

Please select the key to use " "in the identity configuration.

" ""), 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 ComposerDialog::fillKeyResolver() +std::unique_ptr ComposerWindow::fillKeyResolver() { auto keyResolverCore = std::make_unique(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(); recipients.push_back(recipient->email()); } keyResolverCore->setRecipients(recipients); return keyResolverCore; } -void ComposerDialog::slotEncryptionButtonIconUpdate() +void ComposerWindow::slotEncryptionButtonIconUpdate() { const auto state = mEncryptionState.encrypt(); const auto setByUser = mEncryptionState.override(); const auto acceptedSolution = mEncryptionState.acceptedSolution(); 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) { 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) { auto overlay = QIcon::fromTheme(QStringLiteral("emblem-warning")); icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner); } } mEncryptAction->setIcon(icon); mEncryptAction->setToolTip(tooltip); } -void ComposerDialog::runKeyResolver() +void ComposerWindow::runKeyResolver() { auto keyResolverCore = fillKeyResolver(); auto result = keyResolverCore->resolve(); const auto lst = mRecipientEditor->lines(); if (lst.size() == 1) { const auto line = qobject_cast(lst.first()); if (line->recipientsCount() == 0) { mEncryptionState.setAcceptedSolution(false); return; } } mEncryptionState.setAcceptedSolution(result.flags & Kleo::KeyResolverCore::AllResolved); for (auto line_ : lst) { auto line = qobject_cast(line_); Q_ASSERT(line); auto recipient = line->data().dynamicCast(); 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); if (!key.isNull()) { mExpiryChecker->checkKey(key, Kleo::ExpiryChecker::EncryptionKey); } } } -void ComposerDialog::annotateRecipientEditorLineWithCrpytoInfo(RecipientLineNG *line) +void ComposerWindow::annotateRecipientEditorLineWithCrpytoInfo(RecipientLineNG *line) { auto recipient = line->data().dynamicCast(); const auto key = recipient->key(); const auto showCryptoIndicator = true; const auto hasOverride = mEncryptionState.hasOverride(); const auto encrypt = mEncryptionState.encrypt(); const bool showPositiveIcons = showCryptoIndicator && encrypt; const bool showAllIcons = showCryptoIndicator && hasOverride && 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); 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 ComposerDialog::slotSignToggled(bool on) +void ComposerWindow::slotSignToggled(bool on) { setSigning(on, true); updateSignatureAndEncryptionStateIndicators(); } -void ComposerDialog::updateSignatureAndEncryptionStateIndicators() +void ComposerWindow::updateSignatureAndEncryptionStateIndicators() { mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(sign(), mEncryptionState.encrypt()); } -bool ComposerDialog::sign() const +bool ComposerWindow::sign() const { return mSignAction->isChecked(); } -void ComposerDialog::slotSend() +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(); mComposerBase->setCryptoOptions( sign(), encrypt, cryptoMessageFormat()); mComposerBase->send(); } -void ComposerDialog::changeCryptoAction() +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()); } } -void ComposerDialog::slotToggleMarkup() +void ComposerWindow::slotToggleMarkup() { htmlToolBarVisibilityChanged(mMarkupAction->isChecked()); } -void ComposerDialog::htmlToolBarVisibilityChanged(bool visible) +void ComposerWindow::htmlToolBarVisibilityChanged(bool visible) { if (visible) { enableHtml(); } else { disableHtml(LetUserConfirm); } } -void ComposerDialog::enableHtml() +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 ComposerDialog::disableHtml(Confirmation confirmation) +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 ComposerDialog::expiryChecker() +std::shared_ptr ComposerWindow::expiryChecker() { if (!mExpiryChecker) { mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(), encryptKeyNearExpiryWarningThresholdInDays(), encryptRootCertNearExpiryWarningThresholdInDays(), encryptChainCertNearExpiryWarningThresholdInDays()}}); } return mExpiryChecker; } -Kleo::CryptoMessageFormat ComposerDialog::cryptoMessageFormat() const +Kleo::CryptoMessageFormat ComposerWindow::cryptoMessageFormat() const { return Kleo::AutoFormat; } -void ComposerDialog::slotEditIdentity() +void ComposerWindow::slotEditIdentity() { QPointer 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 ComposerDialog::slotIdentityChanged() +void ComposerWindow::slotIdentityChanged() { 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(); } 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(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 ComposerDialog::checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident) +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 ComposerDialog::slotCursorPositionChanged() +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 ComposerDialog::identity() const +KIdentityManagementCore::Identity ComposerWindow::identity() const { return mIdentity; } -void ComposerDialog::addAttachment(const QList &infos, bool showWarning) +void ComposerWindow::addAttachment(const QList &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 ComposerDialog::addAttachment(const QString &name, +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 ComposerDialog::insertUrls(const QMimeData *source, const QList &urlList) +void ComposerWindow::insertUrls(const QMimeData *source, const QList &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 ComposerDialog::insertFromMimeData(const QMimeData *source, bool forceAttachment) +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(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->setComposerDialog(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 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 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 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 ComposerDialog::slotSaveDraft() +void ComposerWindow::slotSaveDraft() { mComposerBase->autoSaveMessage(); } -void ComposerDialog::slotSaveAsFile() +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 ComposerDialog::insertFile() +QUrl ComposerWindow::insertFile() { const auto fileName = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Insert File")); return QUrl::fromUserInput(fileName); } -void ComposerDialog::slotInsertFile() +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 ComposerDialog::slotRecentListFileClear() +void ComposerWindow::slotRecentListFileClear() { MessageComposer::MessageComposerSettings::self()->setRecentUrls({}); MessageComposer::MessageComposerSettings::self()->save(); } -void ComposerDialog::slotInsertRecentFile(const QUrl &u) +void ComposerWindow::slotInsertRecentFile(const QUrl &u) { if (u.fileName().isEmpty()) { return; } auto job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u); job->start(); } -void ComposerDialog::slotPrint() +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 ComposerDialog::slotPrintPreview() +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 ComposerDialog::printInternal(QPrinter *printer) +void ComposerWindow::printInternal(QPrinter *printer) { mComposerBase->setFrom(mFrom); mComposerBase->setSubject(mEdtSubject->text()); mComposerBase->generateMessage([printer](const QList &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 ComposerDialog::slotPasteAsAttachment() +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 ComposerDialog::slotWordWrapToggled(bool on) +void ComposerWindow::slotWordWrapToggled(bool on) { if (on) { mComposerBase->editor()->enableWordWrap(validateLineWrapWidth()); } else { disableWordWrap(); } } -int ComposerDialog::validateLineWrapWidth() const +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 ComposerDialog::disableWordWrap() +void ComposerWindow::disableWordWrap() { mComposerBase->editor()->disableWordWrap(); } -void ComposerDialog::slotAutoSpellCheckingToggled(bool enabled) +void ComposerWindow::slotAutoSpellCheckingToggled(bool enabled) { mAutoSpellCheckingAction->setChecked(enabled); if (mComposerBase->editor()->checkSpellingEnabled() != enabled) { mComposerBase->editor()->setCheckSpellingEnabled(enabled); } //mStatusBarLabelSpellCheckingChangeMode->setToggleMode(enabled); } -void ComposerDialog::slotSpellcheckConfig() +void ComposerWindow::slotSpellcheckConfig() { QPointer 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 ComposerDialog::closeEvent(QCloseEvent *event) +void ComposerWindow::closeEvent(QCloseEvent *event) { event->ignore(); ComposerWindowFactory::self().clear(this); } -bool ComposerDialog::queryClose() +bool ComposerWindow::queryClose() { return KXmlGuiWindow::queryClose(); } diff --git a/server/editor/composerdialog.h b/server/editor/composerwindow.h similarity index 98% rename from server/editor/composerdialog.h rename to server/editor/composerwindow.h index 6b91976..1575895 100644 --- a/server/editor/composerdialog.h +++ b/server/editor/composerwindow.h @@ -1,213 +1,213 @@ // SPDX-FileCopyrightText: 2023 g10 code Gmbh // SPDX-Contributor: Carl Schwan // SPDX-License-Identifier: GPL-2.0-or-later #pragma once // Qt includes #include // KDE includes #include #include #include #include #include #include // 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 ComposerDialog : public KXmlGuiWindow +class ComposerWindow : public KXmlGuiWindow { Q_OBJECT enum Confirmation { LetUserConfirm, NoConfirmationNeeded, }; public: struct AttachmentInfo { QString comment; QUrl url; }; KIdentityManagementCore::Identity identity() const; void addAttachment(const QList &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 insertUrls(const QMimeData *source, const QList &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 expiryChecker(); Q_SIGNALS: void identityChanged(); protected: friend ComposerWindowFactory; - explicit ComposerDialog(const QString &fromAddress, const QString &name, const QByteArray &bearerToken, QWidget *parent = nullptr); + 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 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 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; IdentityCombo * 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 mExpiryChecker; bool mIsModified = false; KRecentFilesAction *mRecentAction = nullptr; KToggleAction *mWordWrapAction = nullptr; KToggleAction *mAutoSpellCheckingAction = nullptr; bool mLastSignActionState = false; std::shared_ptr mKeyCache; bool mLastEncryptActionState = false; }; diff --git a/server/composerwindowfactory.cpp b/server/editor/composerwindowfactory.cpp similarity index 77% rename from server/composerwindowfactory.cpp rename to server/editor/composerwindowfactory.cpp index 30bfc19..25e96d4 100644 --- a/server/composerwindowfactory.cpp +++ b/server/editor/composerwindowfactory.cpp @@ -1,37 +1,37 @@ // SPDX-FileCopyrightText: 2023 g10 code Gmbh // SPDX-Contributor: Carl Schwan // SPDX-License-Identifier: GPL-2.0-or-later #include "composerwindowfactory.h" -#include "editor/composerdialog.h" +#include "editor/composerwindow.h" ComposerWindowFactory::ComposerWindowFactory() = default; ComposerWindowFactory &ComposerWindowFactory::self() { static ComposerWindowFactory instance; return instance; } -ComposerDialog *ComposerWindowFactory::create(const QString &fromAddress, const QString &name, const QByteArray &bearerToken) +ComposerWindow *ComposerWindowFactory::create(const QString &fromAddress, const QString &name, const QByteArray &bearerToken) { if (inactiveWindow) { auto window = inactiveWindow; inactiveWindow = nullptr; window->reset(fromAddress, name, bearerToken); return window; } - return new ComposerDialog(fromAddress, name, bearerToken); + return new ComposerWindow(fromAddress, name, bearerToken); } -void ComposerWindowFactory::clear(ComposerDialog *composerWindow) +void ComposerWindowFactory::clear(ComposerWindow *composerWindow) { if (inactiveWindow) { composerWindow->deleteLater(); return; } inactiveWindow = composerWindow; inactiveWindow->hide(); } diff --git a/server/composerwindowfactory.h b/server/editor/composerwindowfactory.h similarity index 81% rename from server/composerwindowfactory.h rename to server/editor/composerwindowfactory.h index 9a8b7ab..db95aef 100644 --- a/server/composerwindowfactory.h +++ b/server/editor/composerwindowfactory.h @@ -1,35 +1,35 @@ // SPDX-FileCopyrightText: 2023 g10 code Gmbh // SPDX-Contributor: Carl Schwan // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include class QWidget; -class ComposerDialog; +class ComposerWindow; /// Factory to create ComposerWindow. class ComposerWindowFactory { public: /// Get factory singleton. static ComposerWindowFactory &self(); /// Create a new composer dialog. /// /// This might reuse an existing and unused composer dialog instead of creating a /// new one. - ComposerDialog *create(const QString &fromAddress, const QString &name, const QByteArray &bearerToken); + ComposerWindow *create(const QString &fromAddress, const QString &name, const QByteArray &bearerToken); /// Clear composer dialog metadata when not needed anymore. /// /// Ensure that there is always at least one QMainWindow active at the same time /// so that the QApplication is not deleted. - void clear(ComposerDialog *composerWindow); + void clear(ComposerWindow *composerWindow); private: ComposerWindowFactory(); - ComposerDialog * inactiveWindow = nullptr; + ComposerWindow * inactiveWindow = nullptr; }; diff --git a/server/editor/job/dndfromarkjob.cpp b/server/editor/job/dndfromarkjob.cpp index a8d13ce..6529ff2 100644 --- a/server/editor/job/dndfromarkjob.cpp +++ b/server/editor/job/dndfromarkjob.cpp @@ -1,79 +1,79 @@ /* SPDX-FileCopyrightText: 2018-2023 Laurent Montel SPDX-License-Identifier: LGPL-2.0-or-later */ #include "dndfromarkjob.h" -#include "../composerdialog.h" +#include "../composerwindow.h" #include "editor_debug.h" #include #include #include #include #include #include #include DndFromArkJob::DndFromArkJob(QObject *parent) : QObject(parent) { } bool DndFromArkJob::dndFromArk(const QMimeData *source) { if (source->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && source->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { return true; } return false; } bool DndFromArkJob::extract(const QMimeData *source) { bool result = false; if (dndFromArk(source)) { - if (!mComposerDialog) { + if (!mComposerWindow) { qCWarning(EDITOR_LOG) << "mComposerWin is null, it's a bug"; deleteLater(); return result; } const QString remoteDBusClient = QString::fromLatin1(source->data(QStringLiteral("application/x-kde-ark-dndextract-service"))); const QString remoteDBusPath = QString::fromLatin1(source->data(QStringLiteral("application/x-kde-ark-dndextract-path"))); const QString tmpPath = QDir::tempPath() + QLatin1String("/attachments_ark"); QDir().mkpath(tmpPath); auto linkDir = new QTemporaryDir(tmpPath); const QString arkPath = linkDir->path(); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments({arkPath}); QDBusConnection::sessionBus().call(message); QDir dir(arkPath); const QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Files); - QList infoList; + QList infoList; infoList.reserve(list.size()); for (int i = 0; i < list.size(); ++i) { - ComposerDialog::AttachmentInfo info; + ComposerWindow::AttachmentInfo info; info.url = QUrl::fromLocalFile(list.at(i)); infoList.append(info); } - mComposerDialog->addAttachment(infoList, false); + mComposerWindow->addAttachment(infoList, false); delete linkDir; result = true; } deleteLater(); return result; } -void DndFromArkJob::setComposerDialog(ComposerDialog *composerDialog) +void DndFromArkJob::setComposerWindow(ComposerWindow *composerDialog) { - mComposerDialog = composerDialog; + mComposerWindow = composerDialog; } #include "moc_dndfromarkjob.cpp" diff --git a/server/editor/job/dndfromarkjob.h b/server/editor/job/dndfromarkjob.h index f76b74e..22716ec 100644 --- a/server/editor/job/dndfromarkjob.h +++ b/server/editor/job/dndfromarkjob.h @@ -1,24 +1,24 @@ /* SPDX-FileCopyrightText: 2018-2023 Laurent Montel SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include class QMimeData; -class ComposerDialog; +class ComposerWindow; class DndFromArkJob : public QObject { Q_OBJECT public: explicit DndFromArkJob(QObject *parent = nullptr); static bool dndFromArk(const QMimeData *source); [[nodiscard]] bool extract(const QMimeData *source); - void setComposerDialog(ComposerDialog *composerDialog); + void setComposerWindow(ComposerWindow *composerDialog); private: - ComposerDialog *mComposerDialog = nullptr; + ComposerWindow *mComposerWindow = nullptr; }; diff --git a/server/editor/kmcomposerglobalaction.cpp b/server/editor/kmcomposerglobalaction.cpp index d6f076f..7a58efb 100644 --- a/server/editor/kmcomposerglobalaction.cpp +++ b/server/editor/kmcomposerglobalaction.cpp @@ -1,134 +1,134 @@ /* SPDX-FileCopyrightText: 2015-2023 Laurent Montel SPDX-License-Identifier: GPL-2.0-only */ #include "kmcomposerglobalaction.h" -#include "composerdialog.h" +#include "composerwindow.h" #include #include "richtextcomposerng.h" -KMComposerGlobalAction::KMComposerGlobalAction(ComposerDialog *composerWin, QObject *parent) +KMComposerGlobalAction::KMComposerGlobalAction(ComposerWindow *composerWin, QObject *parent) : QObject(parent) , mComposerWin(composerWin) { } KMComposerGlobalAction::~KMComposerGlobalAction() = default; void KMComposerGlobalAction::slotUndo() { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->undo(); } else if (::qobject_cast(fw)) { static_cast(fw)->undo(); } } void KMComposerGlobalAction::slotRedo() { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->redo(); } else if (::qobject_cast(fw)) { static_cast(fw)->redo(); } } void KMComposerGlobalAction::slotCut() { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->cut(); } else if (::qobject_cast(fw)) { static_cast(fw)->cut(); } } void KMComposerGlobalAction::slotCopy() { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->copy(); } else if (::qobject_cast(fw)) { static_cast(fw)->copy(); } } void KMComposerGlobalAction::slotPaste() { QWidget *const fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->paste(); } else if (::qobject_cast(fw)) { static_cast(fw)->paste(); } } void KMComposerGlobalAction::slotMarkAll() { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->selectAll(); } else if (::qobject_cast(fw)) { static_cast(fw)->selectAll(); } } void KMComposerGlobalAction::slotInsertEmoticon(const QString &str) { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->insertPlainText(str); } //} else if (::qobject_cast(fw)) { // Don't insert emoticon in mail linedit // static_cast(fw)->insert(str); } void KMComposerGlobalAction::slotInsertText(const QString &str) { QWidget *fw = mComposerWin->focusWidget(); if (!fw) { return; } if (::qobject_cast(fw)) { static_cast(fw)->insertPlainText(str); } // Don't insert text in mail linedit //} else if (::qobject_cast(fw)) { } #include "moc_kmcomposerglobalaction.cpp" diff --git a/server/editor/kmcomposerglobalaction.h b/server/editor/kmcomposerglobalaction.h index 1b0bb75..5ad4203 100644 --- a/server/editor/kmcomposerglobalaction.h +++ b/server/editor/kmcomposerglobalaction.h @@ -1,31 +1,31 @@ /* SPDX-FileCopyrightText: 2015-2023 Laurent Montel SPDX-License-Identifier: GPL-2.0-only */ #pragma once #include -class ComposerDialog; +class ComposerWindow; class KMComposerGlobalAction : public QObject { Q_OBJECT public: - explicit KMComposerGlobalAction(ComposerDialog *composerDialog, QObject *parent = nullptr); + explicit KMComposerGlobalAction(ComposerWindow *composerDialog, QObject *parent = nullptr); ~KMComposerGlobalAction() override; public Q_SLOTS: void slotUndo(); void slotRedo(); void slotCut(); void slotCopy(); void slotPaste(); void slotMarkAll(); void slotInsertEmoticon(const QString &str); void slotInsertText(const QString &str); private: - ComposerDialog *const mComposerWin; + ComposerWindow *const mComposerWin; };