diff --git a/CMakeLists.txt b/CMakeLists.txt index 28b064d55..1ee5ae102 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,235 +1,235 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16 FATAL_ERROR) set(RELEASE_SERVICE_VERSION_MAJOR "22") set(RELEASE_SERVICE_VERSION_MINOR "07") set(RELEASE_SERVICE_VERSION_MICRO "70") # The RELEASE_SERVICE_VERSION is used by Gpg4win to add the Gpg4win version if (NOT RELEASE_SERVICE_VERSION) set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") endif() if(RELEASE_SERVICE_VERSION_MICRO LESS 10) set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}0${RELEASE_SERVICE_VERSION_MICRO}") else() set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}${RELEASE_SERVICE_VERSION_MICRO}") endif() set(KLEOPATRA_VERSION_MAJOR "3") set(KLEOPATRA_VERSION_MINOR "1") set(KLEOPATRA_VERSION_MICRO "22") set(kleopatra_version "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}.${KDE_APPLICATIONS_COMPACT_VERSION}") # The following is for Windows set(kleopatra_version_win "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}") set(kleopatra_fileversion_win "${KLEOPATRA_VERSION_MAJOR},${KLEOPATRA_VERSION_MINOR},${KLEOPATRA_VERSION_MICRO},0") project(kleopatra VERSION ${kleopatra_version}) option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF) # Standalone build. Find / include everything necessary. set(KF5_MIN_VERSION "5.95.0") set(KIDENTITYMANAGEMENT_VERSION "5.20.40") set(KMAILTRANSPORT_VERSION "5.20.40") set(KMIME_VERSION "5.20.40") -set(LIBKLEO_VERSION "5.20.46") +set(LIBKLEO_VERSION "5.20.47") set(QT_REQUIRED_VERSION "5.15.2") set(GPGME_REQUIRED_VERSION "1.16.0") if (WIN32) set(KF5_WANT_VERSION "5.70.0") set(KMIME_WANT_VERSION "5.12.0") else () set(KF5_WANT_VERSION ${KF5_MIN_VERSION}) set(KMIME_WANT_VERSION ${KMIME_VERSION}) endif () set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(ECM ${KF5_WANT_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddTests) include(GenerateExportHeader) include(ECMGenerateHeaders) include(FeatureSummary) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) include(ECMDeprecationSettings) include(KDEClangFormat) # Find KF5 packages find_package(KF5WidgetsAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5DocTools ${KF5_WANT_VERSION} CONFIG) find_package(KF5Crash ${KF5_WANT_VERSION} REQUIRED) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Documentation tools" PURPOSE "Required to generate Kleopatra documentation." TYPE OPTIONAL) # Optional packages if (WIN32) # Only a replacement available for Windows so this # is required on other platforms. find_package(KF5DBusAddons ${KF5_WANT_VERSION} CONFIG) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus" PURPOSE "DBus session integration" URL "https://inqlude.org/libraries/kdbusaddons.html" TYPE OPTIONAL) else() find_package(KF5DBusAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) set(_kleopatra_dbusaddons_libs KF5::DBusAddons) endif() set(HAVE_QDBUS ${Qt${QT_MAJOR_VERSION}DBus_FOUND}) find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) if (QT_MAJOR_VERSION STREQUAL "6") find_package(QGpgmeQt6 ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) else() find_package(QGpgme ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) endif() if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.17.0") set(QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY 1) set(QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE 1) set(QGPGME_SUPPORTS_WKDLOOKUP 1) set(QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1) set(QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1) set(QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1) set(QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1) set(QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID 1) endif() if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.17.2") set(QGPGME_SUPPORTS_KEY_REVOCATION 1) set(QGPGME_SUPPORTS_KEY_REFRESH 1) set(QGPGME_SUPPORTS_SET_FILENAME 1) endif() # Kdepimlibs packages find_package(KF5Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${KIDENTITYMANAGEMENT_VERSION} CONFIG) find_package(KF5MailTransport ${KMAILTRANSPORT_VERSION} CONFIG) find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_VERSION} CONFIG) find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport) find_package(Assuan2 REQUIRED) set(kleopatra_release FALSE) if(NOT kleopatra_release) find_package(Git) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE rc ERROR_QUIET) if(rc EQUAL 0) execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%h ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE Kleopatra_WC_REVISION) string(REGEX REPLACE "\n" "" Kleopatra_WC_REVISION "${Kleopatra_WC_REVISION}") execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%cI ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE Kleopatra_WC_LAST_CHANGED_DATE) string(REGEX REPLACE "^([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+).*$" "\\1\\2\\3T\\4\\5\\6" Kleopatra_WC_LAST_CHANGED_DATE "${Kleopatra_WC_LAST_CHANGED_DATE}") set(kleopatra_version "${kleopatra_version}+git${Kleopatra_WC_LAST_CHANGED_DATE}~${Kleopatra_WC_REVISION}") endif() endif() endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h) include (ConfigureChecks.cmake) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${ASSUAN2_INCLUDES} ) add_definitions(-D_ASSUAN_ONLY_GPG_ERRORS) if (WIN32) # On Windows, we need to use stuff deprecated since Qt 5.11, e.g. from QDesktopWidget ecm_set_disabled_deprecation_versions(QT 5.10.0 KF 5.96.0) else () ecm_set_disabled_deprecation_versions(QT 5.14.0 KF 5.96.0) endif () if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers") endif() add_definitions(-DQT_NO_EMIT) remove_definitions(-DQT_NO_FOREACH) # Disable the use of QStringBuilder for operator+ to prevent crashes when # returning the result of concatenating string temporaries in lambdas. We do # this for example in some std::transform expressions. # This is a known issue: https://bugreports.qt.io/browse/QTBUG-47066 # Alternatively, one would always have to remember to force the lambdas to # return a QString instead of QStringBuilder, but that's just too easy to # forget and, unfortunately, the compiler doesn't issue a warning if one forgets # this. So, it's just too dangerous. # One can still use QStringBuilder explicitly with the operator% if necessary. remove_definitions(-DQT_USE_FAST_OPERATOR_PLUS) remove_definitions(-DQT_USE_QSTRINGBUILDER) kde_enable_exceptions() option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF) if (USE_UNITY_CMAKE_SUPPORT) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON) endif() add_subdirectory(pics) add_subdirectory(src) if(BUILD_TESTING) add_subdirectory(tests) add_subdirectory(autotests) endif() ecm_qt_install_logging_categories( EXPORT KLEOPATRA FILE kleopatra.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) ki18n_install(po) if(KF5DocTools_FOUND) kdoctools_install(po) add_subdirectory(doc) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) diff --git a/src/crypto/gui/resultitemwidget.cpp b/src/crypto/gui/resultitemwidget.cpp index 510966124..e5220e4d4 100644 --- a/src/crypto/gui/resultitemwidget.cpp +++ b/src/crypto/gui/resultitemwidget.cpp @@ -1,350 +1,360 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resultitemwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "resultitemwidget.h" #include "utils/auditlog.h" #include "commands/command.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/lookupcertificatescommand.h" #include "crypto/decryptverifytask.h" #include "view/htmllabel.h" #include "view/urllabel.h" #include #include +#include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; namespace { // TODO move out of here static QColor colorForVisualCode(Task::Result::VisualCode code) { switch (code) { case Task::Result::AllGood: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color(); case Task::Result::NeutralError: case Task::Result::Warning: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NormalBackground).color(); case Task::Result::Danger: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color(); case Task::Result::NeutralSuccess: default: return QColor(0x00, 0x80, 0xFF); // light blue } } static QColor txtColorForVisualCode(Task::Result::VisualCode code) { switch (code) { case Task::Result::AllGood: case Task::Result::NeutralError: case Task::Result::Warning: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NormalText).color(); case Task::Result::Danger: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); case Task::Result::NeutralSuccess: default: return QColor(0xFF, 0xFF, 0xFF); // white } } } class ResultItemWidget::Private { ResultItemWidget *const q; public: explicit Private(const std::shared_ptr &result, ResultItemWidget *qq) : q(qq), m_result(result) { Q_ASSERT(m_result); } void slotLinkActivated(const QString &); void updateShowDetailsLabel(); void addKeyImportButton(QBoxLayout *lay, bool search); void addIgnoreMDCButton(QBoxLayout *lay); void oneImportFinished(); const std::shared_ptr m_result; UrlLabel *m_auditLogLabel = nullptr; QPushButton *m_closeButton = nullptr; bool m_importCanceled = false; }; void ResultItemWidget::Private::oneImportFinished() { if (m_importCanceled) { return; } if (m_result->parentTask()) { m_result->parentTask()->start(); } q->setVisible(false); } void ResultItemWidget::Private::addIgnoreMDCButton(QBoxLayout *lay) { if (!m_result || !lay) { return; } const auto dvResult = dynamic_cast(m_result.get()); if (!dvResult) { return; } const auto decResult = dvResult->decryptionResult(); if (decResult.isNull() || !decResult.error() || !decResult.isLegacyCipherNoMDC()) { return; } auto btn = new QPushButton(i18n("Force decryption")); btn->setFixedSize(btn->sizeHint()); connect (btn, &QPushButton::clicked, q, [this] () { if (m_result->parentTask()) { const auto dvTask = dynamic_cast(m_result->parentTask().data()); dvTask->setIgnoreMDCError(true); dvTask->start(); q->setVisible(false); } else { qCWarning(KLEOPATRA_LOG) << "Failed to get parent task"; } }); lay->addWidget(btn); } void ResultItemWidget::Private::addKeyImportButton(QBoxLayout *lay, bool search) { if (!m_result || !lay) { return; } const auto dvResult = dynamic_cast(m_result.get()); if (!dvResult) { return; } const auto verifyResult = dvResult->verificationResult(); if (verifyResult.isNull()) { return; } for (const auto &sig: verifyResult.signatures()) { if (!(sig.summary() & GpgME::Signature::KeyMissing)) { continue; } auto btn = new QPushButton; QString suffix; const auto keyid = QLatin1String(sig.fingerprint()); if (verifyResult.numSignatures() > 1) { suffix = QLatin1Char(' ') + keyid; } btn = new QPushButton(search ? i18nc("1 is optional keyid. No space is intended as it can be empty.", "Search%1", suffix) : i18nc("1 is optional keyid. No space is intended as it can be empty.", "Import%1", suffix)); if (search) { btn->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect (btn, &QPushButton::clicked, q, [this, btn, keyid] () { btn->setEnabled(false); m_importCanceled = false; auto cmd = new Kleo::Commands::LookupCertificatesCommand(keyid, nullptr); connect(cmd, &Kleo::Commands::LookupCertificatesCommand::canceled, q, [this]() { m_importCanceled = true; }); connect(cmd, &Kleo::Commands::LookupCertificatesCommand::finished, q, [this, btn]() { btn->setEnabled(true); oneImportFinished(); }); cmd->setParentWidget(q); cmd->start(); }); } else { btn->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import"))); connect (btn, &QPushButton::clicked, q, [this, btn] () { btn->setEnabled(false); m_importCanceled = false; auto cmd = new Kleo::ImportCertificateFromFileCommand(); connect(cmd, &Kleo::ImportCertificateFromFileCommand::canceled, q, [this]() { m_importCanceled = true; }); connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [this, btn]() { btn->setEnabled(true); oneImportFinished(); }); cmd->setParentWidget(q); cmd->start(); }); } btn->setFixedSize(btn->sizeHint()); lay->addWidget(btn); } } static QUrl auditlog_url_template() { QUrl url(QStringLiteral("kleoresultitem://showauditlog")); return url; } void ResultItemWidget::Private::updateShowDetailsLabel() { const auto auditLogUrl = m_result->auditLog().asUrl(auditlog_url_template()); const auto auditLogLinkText = m_result->hasError() ? i18n("Diagnostics") : i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log"); m_auditLogLabel->setUrl(auditLogUrl, auditLogLinkText); m_auditLogLabel->setVisible(!auditLogUrl.isEmpty()); } ResultItemWidget::ResultItemWidget(const std::shared_ptr &result, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), d(new Private(result, this)) { const QColor color = colorForVisualCode(d->m_result->code()); const QColor txtColor = txtColorForVisualCode(d->m_result->code()); const QColor linkColor = txtColor; - const QString styleSheet = QStringLiteral("QFrame,QLabel { background-color: %1; margin: 0px; }" - "QFrame#resultFrame{ border-color: %2; border-style: solid; border-radius: 3px; border-width: 1px }" - "QLabel { color: %3; padding: 5px; border-radius: 3px }").arg(color.name()).arg(color.darker(150).name()).arg(txtColor.name()); + const QString styleSheet = SystemInfo::isHighContrastModeActive() + ? QStringLiteral( + "QFrame,QLabel { margin: 0px; }" + "QFrame#resultFrame{ border-style: solid; border-radius: 3px; border-width: 1px }" + "QLabel { padding: 5px; border-radius: 3px }") + : QStringLiteral( + "QFrame,QLabel { background-color: %1; margin: 0px; }" + "QFrame#resultFrame{ border-color: %2; border-style: solid; border-radius: 3px; border-width: 1px }" + "QLabel { color: %3; padding: 5px; border-radius: 3px }") + .arg(color.name()) + .arg(color.darker(150).name()) + .arg(txtColor.name()); auto topLayout = new QVBoxLayout(this); auto frame = new QFrame; frame->setObjectName(QStringLiteral("resultFrame")); frame->setStyleSheet(styleSheet); topLayout->addWidget(frame); auto layout = new QHBoxLayout(frame); auto vlay = new QVBoxLayout(); auto overview = new HtmlLabel; overview->setWordWrap(true); overview->setHtml(d->m_result->overview()); overview->setStyleSheet(styleSheet); overview->setLinkColor(linkColor); setFocusPolicy(overview->focusPolicy()); setFocusProxy(overview); connect(overview, &QLabel::linkActivated, this, [this](const auto &link) { d->slotLinkActivated(link); }); vlay->addWidget(overview); layout->addLayout(vlay); auto actionLayout = new QVBoxLayout; layout->addLayout(actionLayout); d->addKeyImportButton(actionLayout, false); // TODO: Only show if auto-key-retrieve is not set. d->addKeyImportButton(actionLayout, true); d->addIgnoreMDCButton(actionLayout); d->m_auditLogLabel = new UrlLabel; connect(d->m_auditLogLabel, &QLabel::linkActivated, this, [this](const auto &link) { d->slotLinkActivated(link); }); actionLayout->addWidget(d->m_auditLogLabel); d->m_auditLogLabel->setStyleSheet(styleSheet); d->m_auditLogLabel->setLinkColor(linkColor); auto detailsLabel = new HtmlLabel; detailsLabel->setWordWrap(true); detailsLabel->setHtml(d->m_result->details()); detailsLabel->setStyleSheet(styleSheet); detailsLabel->setLinkColor(linkColor); connect(detailsLabel, &QLabel::linkActivated, this, [this](const auto &link) { d->slotLinkActivated(link); }); vlay->addWidget(detailsLabel); d->m_closeButton = new QPushButton; KGuiItem::assign(d->m_closeButton, KStandardGuiItem::close()); d->m_closeButton->setFixedSize(d->m_closeButton->sizeHint()); connect(d->m_closeButton, &QAbstractButton::clicked, this, &ResultItemWidget::closeButtonClicked); actionLayout->addWidget(d->m_closeButton); d->m_closeButton->setVisible(false); layout->setStretch(0, 1); actionLayout->addStretch(-1); vlay->addStretch(-1); d->updateShowDetailsLabel(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum); } ResultItemWidget::~ResultItemWidget() { } void ResultItemWidget::showCloseButton(bool show) { d->m_closeButton->setVisible(show); } bool ResultItemWidget::hasErrorResult() const { return d->m_result->hasError(); } void ResultItemWidget::Private::slotLinkActivated(const QString &link) { Q_ASSERT(m_result); qCDebug(KLEOPATRA_LOG) << "Link activated: " << link; if (link.startsWith(QLatin1String("key:"))) { auto split = link.split(QLatin1Char(':')); auto fpr = split.value(1); if (split.size() == 2 && isFingerprint(fpr)) { /* There might be a security consideration here if somehow * a short keyid is used in a link and it collides with another. * So we additionally check that it really is a fingerprint. */ auto cmd = Command::commandForQuery(fpr); cmd->setParentWId(q->effectiveWinId()); cmd->start(); } else { qCWarning(KLEOPATRA_LOG) << "key link invalid " << link; } return; } const QUrl url(link); if (url.host() == QLatin1String("showauditlog")) { q->showAuditLog(); return; } qCWarning(KLEOPATRA_LOG) << "Unexpected link scheme: " << link; } void ResultItemWidget::showAuditLog() { MessageBox::auditLog(parentWidget(), d->m_result->auditLog().text()); } #include "moc_resultitemwidget.cpp" diff --git a/src/crypto/gui/signencryptemailconflictdialog.cpp b/src/crypto/gui/signencryptemailconflictdialog.cpp index b522270df..016bd8afd 100644 --- a/src/crypto/gui/signencryptemailconflictdialog.cpp +++ b/src/crypto/gui/signencryptemailconflictdialog.cpp @@ -1,645 +1,648 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signencryptemailconflictdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptemailconflictdialog.h" #include #include #include "dialogs/certificateselectiondialog.h" #include "certificateselectionline.h" #include #include "utils/gui-helper.h" #include "utils/kleo_assert.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace Kleo::Dialogs; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(GpgME::UserID) static CertificateSelectionDialog * create_certificate_selection_dialog(QWidget *parent, Protocol proto) { auto const dlg = new CertificateSelectionDialog(parent); dlg->setOptions(proto == OpenPGP ? CertificateSelectionDialog::OpenPGPFormat : proto == CMS ? CertificateSelectionDialog::CMSFormat : CertificateSelectionDialog::AnyFormat); return dlg; } static CertificateSelectionDialog * create_encryption_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox) { CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto); dlg->setCustomLabelText(i18n("Please select an encryption certificate for recipient \"%1\"", mailbox)); dlg->setOptions(CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::EncryptOnly | dlg->options()); return dlg; } static CertificateSelectionDialog * create_signing_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox) { CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto); dlg->setCustomLabelText(i18n("Please select a signing certificate for sender \"%1\"", mailbox)); dlg->setOptions(CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::SignOnly | CertificateSelectionDialog::SecretKeys | dlg->options()); return dlg; } static QString make_top_label_conflict_text(bool sign, bool enc) { return sign && enc ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for all recipients/senders of the message.\n" "Please select the correct certificates for each recipient:") : sign ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for the sender of the message.\n" "Please select the correct certificates for the sender:") : enc ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for all recipients of the message.\n" "Please select the correct certificates for each recipient:") : /* else */ (kleo_assert_fail(sign || enc), QString()); } static QString make_top_label_quickmode_text(bool sign, bool enc) { return enc ? i18n("Please verify that correct certificates have been selected for each recipient:") : sign ? i18n("Please verify that the correct certificate has been selected for the sender:") : /*else*/ (kleo_assert_fail(sign || enc), QString()); } class SignEncryptEMailConflictDialog::Private { friend class ::Kleo::Crypto::Gui::SignEncryptEMailConflictDialog; SignEncryptEMailConflictDialog *const q; public: explicit Private(SignEncryptEMailConflictDialog *qq) : q(qq), senders(), recipients(), sign(true), encrypt(true), presetProtocol(UnknownProtocol), ui(q) { } private: void updateTopLabelText() { ui.conflictTopLB.setText(make_top_label_conflict_text(sign, encrypt)); ui.quickModeTopLB.setText(make_top_label_quickmode_text(sign, encrypt)); } void showHideWidgets() { const Protocol proto = q->selectedProtocol(); const bool quickMode = q->isQuickMode(); const bool needProtocolSelection = presetProtocol == UnknownProtocol; const bool needShowAllRecipientsCB = quickMode ? false : needProtocolSelection ? needShowAllRecipients(OpenPGP) || needShowAllRecipients(CMS) : /* else */ needShowAllRecipients(proto) ; ui.showAllRecipientsCB.setVisible(needShowAllRecipientsCB); ui.pgpRB.setVisible(needProtocolSelection); ui.cmsRB.setVisible(needProtocolSelection); const bool showAll = !needShowAllRecipientsCB || ui.showAllRecipientsCB.isChecked(); bool first; first = true; for (const CertificateSelectionLine &line : std::as_const(ui.signers)) { line.showHide(proto, first, showAll, sign); } ui.selectSigningCertificatesGB.setVisible(sign && (showAll || !first)); first = true; for (const CertificateSelectionLine &line : std::as_const(ui.recipients)) { line.showHide(proto, first, showAll, encrypt); } ui.selectEncryptionCertificatesGB.setVisible(encrypt && (showAll || !first)); } bool needShowAllRecipients(Protocol proto) const { if (sign) { if (const unsigned int num = std::count_if(ui.signers.cbegin(), ui.signers.cend(), [proto](const CertificateSelectionLine &l) { return l.wasInitiallyAmbiguous(proto); })) { if (num != ui.signers.size()) { return true; } } } if (encrypt) { if (const unsigned int num = std::count_if(ui.recipients.cbegin(), ui.recipients.cend(), [proto](const CertificateSelectionLine &l) { return l.wasInitiallyAmbiguous(proto); })) { if (num != ui.recipients.size()) { return true; } } } return false; } void createSendersAndRecipients() { ui.clearSendersAndRecipients(); ui.addSelectSigningCertificatesGB(); for (const Sender &s : std::as_const(senders)) { addSigner(s); } ui.addSelectEncryptionCertificatesGB(); for (const Sender &s : std::as_const(senders)) { addRecipient(s); } for (const Recipient &r : std::as_const(recipients)) { addRecipient(r); } } void addSigner(const Sender &s) { ui.addSigner(s.mailbox().prettyAddress(), s.signingCertificateCandidates(OpenPGP), s.isSigningAmbiguous(OpenPGP), s.signingCertificateCandidates(CMS), s.isSigningAmbiguous(CMS), q); } void addRecipient(const Sender &s) { ui.addRecipient(s.mailbox().prettyAddress(), s.encryptToSelfCertificateCandidates(OpenPGP), s.isEncryptionAmbiguous(OpenPGP), s.encryptToSelfCertificateCandidates(CMS), s.isEncryptionAmbiguous(CMS), q); } void addRecipient(const Recipient &r) { ui.addRecipient(r.mailbox().prettyAddress(), r.encryptionCertificateCandidates(OpenPGP), r.isEncryptionAmbiguous(OpenPGP), r.encryptionCertificateCandidates(CMS), r.isEncryptionAmbiguous(CMS), q); } bool isComplete(Protocol proto) const; private: void updateComplianceStatus() { if (!Kleo::gnupgIsDeVsCompliant()) { return; } if (q->selectedProtocol() == UnknownProtocol || (q->resolvedSigningKeys().empty() && q->resolvedEncryptionKeys().empty())) { return; } // Handle compliance bool de_vs = true; for (const auto &key: q->resolvedSigningKeys()) { if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } if (de_vs) { for (const auto &key: q->resolvedEncryptionKeys()) { if (!key.isDeVs() || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } } auto btn = ui.buttonBox.button(QDialogButtonBox::Ok); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); - btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs - ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() - : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); + if (!SystemInfo::isHighContrastModeActive()) { + btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs + ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() + : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); + } ui.complianceLB.setText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); ui.complianceLB.setVisible(true); } void updateDialogStatus() { ui.setOkButtonEnabled(q->isComplete()); updateComplianceStatus(); } void slotCompleteChanged() { updateDialogStatus(); } void slotShowAllRecipientsToggled(bool) { showHideWidgets(); } void slotProtocolChanged() { showHideWidgets(); updateDialogStatus(); } void slotCertificateSelectionDialogRequested() { const QObject *const s = q->sender(); const Protocol proto = q->selectedProtocol(); QPointer dlg; for (const CertificateSelectionLine &l : std::as_const(ui.signers)) if (s == l.toolButton()) { dlg = create_signing_certificate_selection_dialog(q, proto, l.mailboxText()); if (dlg->exec()) { l.addAndSelectCertificate(dlg->selectedCertificate()); } // ### switch to key.protocol(), in case proto == UnknownProtocol break; } for (const CertificateSelectionLine &l : std::as_const(ui.recipients)) if (s == l.toolButton()) { dlg = create_encryption_certificate_selection_dialog(q, proto, l.mailboxText()); if (dlg->exec()) { l.addAndSelectCertificate(dlg->selectedCertificate()); } // ### switch to key.protocol(), in case proto == UnknownProtocol break; } #ifndef Q_OS_WIN // This leads to a crash on Windows. We don't really // leak memory here anyway because the destruction of the // dialog happens when the parent (q) is destroyed anyway. delete dlg; #endif } private: std::vector senders; std::vector recipients; bool sign : 1; bool encrypt : 1; Protocol presetProtocol; private: struct Ui { QLabel conflictTopLB, quickModeTopLB; QCheckBox showAllRecipientsCB; QRadioButton pgpRB, cmsRB; QGroupBox selectSigningCertificatesGB; QGroupBox selectEncryptionCertificatesGB; QCheckBox quickModeCB; QDialogButtonBox buttonBox; QVBoxLayout vlay; QHBoxLayout hlay; QHBoxLayout hlay2; QGridLayout glay; std::vector signers, recipients; QLabel complianceLB; void setOkButtonEnabled(bool enable) { return buttonBox.button(QDialogButtonBox::Ok)->setEnabled(enable); } explicit Ui(SignEncryptEMailConflictDialog *q) : conflictTopLB(make_top_label_conflict_text(true, true), q), quickModeTopLB(make_top_label_quickmode_text(true, true), q), showAllRecipientsCB(i18n("Show all recipients"), q), pgpRB(i18n("OpenPGP"), q), cmsRB(i18n("S/MIME"), q), selectSigningCertificatesGB(i18n("Select Signing Certificate"), q), selectEncryptionCertificatesGB(i18n("Select Encryption Certificate"), q), quickModeCB(i18n("Only show this dialog in case of conflicts (experimental)"), q), buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q), vlay(q), hlay(), glay(), signers(), recipients() { KDAB_SET_OBJECT_NAME(conflictTopLB); KDAB_SET_OBJECT_NAME(quickModeTopLB); KDAB_SET_OBJECT_NAME(showAllRecipientsCB); KDAB_SET_OBJECT_NAME(pgpRB); KDAB_SET_OBJECT_NAME(cmsRB); KDAB_SET_OBJECT_NAME(selectSigningCertificatesGB); KDAB_SET_OBJECT_NAME(selectEncryptionCertificatesGB); KDAB_SET_OBJECT_NAME(quickModeCB); KDAB_SET_OBJECT_NAME(buttonBox); KDAB_SET_OBJECT_NAME(hlay); KDAB_SET_OBJECT_NAME(glay); KDAB_SET_OBJECT_NAME(vlay); q->setWindowTitle(i18nc("@title:window", "Select Certificates for Message")); conflictTopLB.hide(); selectSigningCertificatesGB.setFlat(true); selectEncryptionCertificatesGB.setFlat(true); selectSigningCertificatesGB.setAlignment(Qt::AlignCenter); selectEncryptionCertificatesGB.setAlignment(Qt::AlignCenter); glay.setColumnStretch(2, 1); glay.setColumnStretch(3, 1); vlay.setSizeConstraint(QLayout::SetMinimumSize); vlay.addWidget(&conflictTopLB); vlay.addWidget(&quickModeTopLB); hlay.addWidget(&showAllRecipientsCB); hlay.addStretch(1); hlay.addWidget(&pgpRB); hlay.addWidget(&cmsRB); vlay.addLayout(&hlay); addSelectSigningCertificatesGB(); addSelectEncryptionCertificatesGB(); vlay.addLayout(&glay); vlay.addStretch(1); complianceLB.setVisible(false); hlay2.addStretch(1); hlay2.addWidget(&complianceLB, 0, Qt::AlignRight); hlay2.addWidget(&buttonBox, 0, Qt::AlignRight); vlay.addWidget(&quickModeCB, 0, Qt::AlignRight); vlay.addLayout(&hlay2); connect(&buttonBox, &QDialogButtonBox::accepted, q, &SignEncryptEMailConflictDialog::accept); connect(&buttonBox, &QDialogButtonBox::rejected, q, &SignEncryptEMailConflictDialog::reject); connect(&showAllRecipientsCB, SIGNAL(toggled(bool)), q, SLOT(slotShowAllRecipientsToggled(bool))); connect(&pgpRB, SIGNAL(toggled(bool)), q, SLOT(slotProtocolChanged())); connect(&cmsRB, SIGNAL(toggled(bool)), q, SLOT(slotProtocolChanged())); } void clearSendersAndRecipients() { std::vector sig, enc; sig.swap(signers); enc.swap(recipients); std::for_each(sig.begin(), sig.end(), std::mem_fn(&CertificateSelectionLine::kill)); std::for_each(enc.begin(), enc.end(), std::mem_fn(&CertificateSelectionLine::kill)); glay.removeWidget(&selectSigningCertificatesGB); glay.removeWidget(&selectEncryptionCertificatesGB); } void addSelectSigningCertificatesGB() { glay.addWidget(&selectSigningCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns); } void addSelectEncryptionCertificatesGB() { glay.addWidget(&selectEncryptionCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns); } void addSigner(const QString &mailbox, const std::vector &pgp, bool pgpAmbiguous, const std::vector &cms, bool cmsAmbiguous, QWidget *q) { CertificateSelectionLine line(i18n("From:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay); signers.push_back(line); } void addRecipient(const QString &mailbox, const std::vector &pgp, bool pgpAmbiguous, const std::vector &cms, bool cmsAmbiguous, QWidget *q) { CertificateSelectionLine line(i18n("To:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay); recipients.push_back(line); } } ui; }; SignEncryptEMailConflictDialog::SignEncryptEMailConflictDialog(QWidget *parent) : QDialog(parent), d(new Private(this)) { } SignEncryptEMailConflictDialog::~SignEncryptEMailConflictDialog() {} void SignEncryptEMailConflictDialog::setPresetProtocol(Protocol p) { if (p == d->presetProtocol) { return; } const QSignalBlocker pgpBlocker(d->ui.pgpRB); const QSignalBlocker cmsBlocker(d->ui.cmsRB); really_check(d->ui.pgpRB, p == OpenPGP); really_check(d->ui.cmsRB, p == CMS); d->presetProtocol = p; d->showHideWidgets(); d->updateDialogStatus(); } Protocol SignEncryptEMailConflictDialog::selectedProtocol() const { if (d->presetProtocol != UnknownProtocol) { return d->presetProtocol; } if (d->ui.pgpRB.isChecked()) { return OpenPGP; } if (d->ui.cmsRB.isChecked()) { return CMS; } return UnknownProtocol; } void SignEncryptEMailConflictDialog::setSubject(const QString &subject) { setWindowTitle(i18nc("@title:window", "Select Certificates for Message \"%1\"", subject)); } void SignEncryptEMailConflictDialog::setSign(bool sign) { if (sign == d->sign) { return; } d->sign = sign; d->updateTopLabelText(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setEncrypt(bool encrypt) { if (encrypt == d->encrypt) { return; } d->encrypt = encrypt; d->updateTopLabelText(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setSenders(const std::vector &senders) { if (senders == d->senders) { return; } d->senders = senders; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setRecipients(const std::vector &recipients) { if (d->recipients == recipients) { return; } d->recipients = recipients; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::pickProtocol() { if (selectedProtocol() != UnknownProtocol) { return; // already picked } const bool pgp = d->isComplete(OpenPGP); const bool cms = d->isComplete(CMS); if (pgp && !cms) { d->ui.pgpRB.setChecked(true); } else if (cms && !pgp) { d->ui.cmsRB.setChecked(true); } } bool SignEncryptEMailConflictDialog::isComplete() const { const Protocol proto = selectedProtocol(); return proto != UnknownProtocol && d->isComplete(proto); } bool SignEncryptEMailConflictDialog::Private::isComplete(Protocol proto) const { return (!sign || std::none_of(ui.signers.cbegin(), ui.signers.cend(), [proto](const CertificateSelectionLine &l) { return l.isStillAmbiguous(proto); })) && (!encrypt || std::none_of(ui.recipients.cbegin(), ui.recipients.cend(), [proto](const CertificateSelectionLine &l) { return l.isStillAmbiguous(proto); })); } static std::vector get_keys(const std::vector &lines, Protocol proto) { if (proto == UnknownProtocol) { return std::vector(); } Q_ASSERT(proto == OpenPGP || proto == CMS); std::vector keys; keys.reserve(lines.size()); std::transform(lines.cbegin(), lines.cend(), std::back_inserter(keys), [proto](const CertificateSelectionLine &l) { return l.key(proto); }); return keys; } std::vector SignEncryptEMailConflictDialog::resolvedSigningKeys() const { return d->sign ? get_keys(d->ui.signers, selectedProtocol()) : std::vector(); } std::vector SignEncryptEMailConflictDialog::resolvedEncryptionKeys() const { return d->encrypt ? get_keys(d->ui.recipients, selectedProtocol()) : std::vector(); } void SignEncryptEMailConflictDialog::setQuickMode(bool on) { d->ui.quickModeCB.setChecked(on); } bool SignEncryptEMailConflictDialog::isQuickMode() const { return d->ui.quickModeCB.isChecked(); } void SignEncryptEMailConflictDialog::setConflict(bool conflict) { d->ui.conflictTopLB.setVisible(conflict); d->ui.quickModeTopLB.setVisible(!conflict); } #include "moc_signencryptemailconflictdialog.cpp" diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp index a0c9980f2..01d52aabe 100644 --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -1,689 +1,692 @@ /* crypto/gui/signencryptfileswizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfileswizard.h" #include "signencryptwidget.h" #include "newresultpage.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto::Gui; enum Page { SigEncPageId, ResultPageId, NumPages }; class FileNameRequesterWithIcon : public QWidget { Q_OBJECT public: explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr) : QWidget(parent) { auto layout = new QHBoxLayout{this}; layout->setContentsMargins(0, 0, 0, 0); mIconLabel = new QLabel{this}; mRequester = new FileNameRequester{filter, this}; mRequester->setExistingOnly(false); layout->addWidget(mIconLabel); layout->addWidget(mRequester); setFocusPolicy(mRequester->focusPolicy()); setFocusProxy(mRequester); connect(mRequester, &FileNameRequester::fileNameChanged, this, &FileNameRequesterWithIcon::fileNameChanged); } void setIcon(const QIcon &icon) { mIconLabel->setPixmap(icon.pixmap(32, 32)); } void setFileName(const QString &name) { mRequester->setFileName(name); } QString fileName() const { return mRequester->fileName(); } void setNameFilter(const QString &nameFilter) { mRequester->setNameFilter(nameFilter); } QString nameFilter() const { return mRequester->nameFilter(); } FileNameRequester *requester() { return mRequester; } Q_SIGNALS: void fileNameChanged(const QString &filename); protected: bool event(QEvent *e) override { if (e->type() == QEvent::ToolTipChange) { mRequester->setToolTip(toolTip()); } return QWidget::event(e); } private: QLabel *mIconLabel; FileNameRequester *mRequester; }; class SigEncPage: public QWizardPage { Q_OBJECT public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent), mParent((SignEncryptFilesWizard *) parent), mWidget(new SignEncryptWidget), mOutLayout(new QVBoxLayout), mOutputLabel{nullptr}, mArchive(false), mUseOutputDir(false), mSingleFile{true} { setTitle(i18nc("@title", "Sign / Encrypt Files")); auto vLay = new QVBoxLayout(this); vLay->setContentsMargins(0, 0, 0, 0); if (!Settings{}.cmsEnabled()) { mWidget->setProtocol(GpgME::OpenPGP); } mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage", "&Sign as:")); mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage", "Encrypt for &me:")); mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage", "Encrypt for &others:")); mWidget->setEncryptWithPasswordText(i18nc("@option:check on SignEncryptPage", "Encrypt with &password. Anyone you share the password with can read the data.")); vLay->addWidget(mWidget); connect(mWidget, &SignEncryptWidget::operationChanged, this, &SigEncPage::updateCommitButton); connect(mWidget, &SignEncryptWidget::keysChanged, this, &SigEncPage::updateFileWidgets); auto outputGrp = new QGroupBox(i18nc("@title:group", "Output")); outputGrp->setLayout(mOutLayout); mPlaceholderWidget = new QLabel(i18n("Please select an action.")); mOutLayout->addWidget(mPlaceholderWidget); mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:")); mOutLayout->addWidget(mOutputLabel); createRequesters(mOutLayout); mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage", "Encrypt / Sign &each file separately.")); mUseOutputDirChk->setToolTip(i18nc("@info:tooltip", "Keep each file separate instead of creating an archive for all.")); mOutLayout->addWidget(mUseOutputDirChk); connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) { mUseOutputDir = state; mArchive = !mUseOutputDir && !mSingleFile; updateFileWidgets(); }); vLay->addWidget(outputGrp); auto messageWidget = new KMessageWidget; messageWidget->setMessageType(KMessageWidget::Error); messageWidget->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this)); messageWidget->setText(i18n("Signing and encrypting files is not possible.")); messageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", Formatting::deVsString())); messageWidget->setCloseButtonVisible(false); messageWidget->setVisible(Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()); vLay->addWidget(messageWidget); setMinimumHeight(300); } void setEncryptionPreset(bool value) { mWidget->setEncryptionChecked(value); } void setSigningPreset(bool value) { mWidget->setSigningChecked(value); } bool isComplete() const override { if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { return false; } return mWidget->isComplete(); } int nextId() const override { return ResultPageId; } void initializePage() override { setCommitPage(true); updateCommitButton(mWidget->currentOp()); } void setArchiveForced(bool archive) { mArchive = archive; setArchiveMutable(!archive); } void setArchiveMutable(bool archive) { mUseOutputDirChk->setVisible(archive); if (archive) { const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false)); } else { mUseOutputDirChk->setChecked(false); } } void setSingleFile(bool singleFile) { mSingleFile = singleFile; mArchive = !mUseOutputDir && !mSingleFile; } bool validatePage() override { if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { KMessageBox::sorry(topLevelWidget(), xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "Sorry! You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", Formatting::deVsString())); return false; } bool sign = !mWidget->signKey().isNull(); bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty(); if (!mWidget->validate()) { return false; } mWidget->saveOwnKeys(); if (mUseOutputDirChk->isVisible()) { KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); archCfg.writeEntry("LastUseOutputDir", mUseOutputDir); } if (sign && !encrypt && mArchive) { return KMessageBox::warningContinueCancel(this, xi18nc("@info", "Archiving in combination with sign-only currently requires what are known as opaque signatures - " "unlike detached ones, these embed the content in the signature." "This format is rather unusual. You might want to archive the files separately, " "and then sign the archive as one file with Kleopatra." "Future versions of Kleopatra are expected to also support detached signatures in this case."), i18nc("@title:window", "Unusual Signature Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("signencryptfileswizard-archive+sign-only-warning")) == KMessageBox::Continue; } else if (sign && !encrypt) { return true; } if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) { return true; } const auto recipientKeys = recipients(); const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys), [](const auto &k) { return k.hasSecret(); }); if (!hasSecret) { if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "None of the recipients you are encrypting to seems to be your own." "This means that you will not be able to decrypt the data anymore, once encrypted." "Do you want to continue, or cancel to change the recipient selection?"), i18nc("@title:window", "Encrypt-To-Self Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Cancel) { return false; } } return true; } std::vector recipients() const { return mWidget->recipients(); } /* In the future we might find a usecase for multiple * signers */ std::vector signers() const { const Key k = mWidget->signKey(); if (!k.isNull()) { return {k}; } return {}; } private: struct RequesterInfo { SignEncryptFilesWizard::KindNames id; QString icon; QString toolTip; QString accessibleName; QString nameFilterBinary; QString nameFilterAscii; }; void createRequesters(QBoxLayout *lay) { static const std::array requestersInfo = {{ { SignEncryptFilesWizard::SignatureCMS, QStringLiteral("document-sign"), i18nc("@info:tooltip", "This is the filename of the S/MIME signature."), i18nc("Lineedit accessible name", "S/MIME signature file"), i18nc("Name filter binary", "S/MIME Signatures (*.p7s)"), i18nc("Name filter ASCII", "S/MIME Signatures (*.p7s *.pem)"), }, { SignEncryptFilesWizard::SignaturePGP, QStringLiteral("document-sign"), i18nc("@info:tooltip", "This is the filename of the detached OpenPGP signature."), i18nc("Lineedit accessible name", "OpenPGP signature file"), i18nc("Name filter binary", "OpenPGP Signatures (*.sig *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Signatures (*.asc *.sig)"), }, { SignEncryptFilesWizard::CombinedPGP, QStringLiteral("document-edit-sign-encrypt"), i18nc("@info:tooltip", "This is the filename of the OpenPGP-signed and encrypted file."), i18nc("Lineedit accessible name", "OpenPGP signed and encrypted file"), i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"), }, { SignEncryptFilesWizard::EncryptedPGP, QStringLiteral("document-encrypt"), i18nc("@info:tooltip", "This is the filename of the OpenPGP encrypted file."), i18nc("Lineedit accessible name", "OpenPGP encrypted file"), i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"), i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"), }, { SignEncryptFilesWizard::EncryptedCMS, QStringLiteral("document-encrypt"), i18nc("@info:tooltip", "This is the filename of the S/MIME encrypted file."), i18nc("Lineedit accessible name", "S/MIME encrypted file"), i18nc("Name filter binary", "S/MIME Files (*.p7m)"), i18nc("Name filter ASCII", "S/MIME Files (*.p7m *.pem)"), }, { SignEncryptFilesWizard::Directory, QStringLiteral("folder"), i18nc("@info:tooltip", "The resulting files are written to this directory."), i18nc("Lineedit accessible name", "Output directory"), {}, {}, }, }}; if (!mRequesters.empty()) { return; } const bool isAscii = FileOperationsPreferences().addASCIIArmor(); for (const auto &requester : requestersInfo) { const auto id = requester.id; auto requesterWithIcon = new FileNameRequesterWithIcon{id == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this}; requesterWithIcon->setIcon(QIcon::fromTheme(requester.icon)); requesterWithIcon->setToolTip(requester.toolTip); requesterWithIcon->requester()->setAccessibleNameOfLineEdit(requester.accessibleName); requesterWithIcon->setNameFilter(isAscii ? requester.nameFilterAscii : requester.nameFilterBinary); lay->addWidget(requesterWithIcon); connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, id](const QString &newName) { mOutNames[id] = newName; }); mRequesters.insert(id, requesterWithIcon); } } public: void setOutputNames(const QMap &names) { Q_ASSERT(mOutNames.isEmpty()); for (auto it = std::begin(names); it != std::end(names); ++it) { mRequesters.value(it.key())->setFileName(it.value()); } mOutNames = names; updateFileWidgets(); } QMap outputNames() const { if (!mUseOutputDir) { auto ret = mOutNames; ret.remove(SignEncryptFilesWizard::Directory); return ret; } return mOutNames; } bool encryptSymmetric() const { return mWidget->encryptSymmetric(); } private Q_SLOTS: void updateCommitButton(const QString &label) { if (mParent->currentPage() != this) { return; } auto btn = mParent->button(QWizard::CommitButton); if (!label.isEmpty()) { mParent->setButtonText(QWizard::CommitButton, label); if (Kleo::gnupgUsesDeVsCompliance()) { const bool de_vs = Kleo::gnupgIsDeVsCompliant() && mWidget->isDeVsAndValid(); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); - btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs - ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() - : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); + if (!SystemInfo::isHighContrastModeActive()) { + btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs + ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() + : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); + } mParent->setLabelText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); } } else { mParent->setButtonText(QWizard::CommitButton, i18n("Next")); btn->setIcon(QIcon()); btn->setStyleSheet(QString()); } Q_EMIT completeChanged(); } void updateFileWidgets() { if (mRequesters.isEmpty()) { return; } const std::vector recipients = mWidget->recipients(); const Key sigKey = mWidget->signKey(); const bool pgp = mWidget->encryptSymmetric() || std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::OpenPGP; }); const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::CMS; }); mOutLayout->setEnabled(false); if (cms || pgp || !sigKey.isNull()) { mPlaceholderWidget->setVisible(false); mOutputLabel->setVisible(true); mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS); mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms); mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp); mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir); auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { return !w->isHidden(); }); mOutputLabel->setBuddy(*firstNotHidden); } else { mPlaceholderWidget->setVisible(true); mOutputLabel->setVisible(false); std::for_each(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { w->setVisible(false); }); mOutputLabel->setBuddy(nullptr); } mOutLayout->setEnabled(true); } private: SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap mOutNames; QMap mRequesters; QVBoxLayout *mOutLayout; QWidget *mPlaceholderWidget; QCheckBox *mUseOutputDirChk; QLabel *mOutputLabel; bool mArchive; bool mUseOutputDir; bool mSingleFile; }; class ResultPage : public NewResultPage { Q_OBJECT public: explicit ResultPage(QWidget *parent = nullptr) : NewResultPage(parent), mParent((SignEncryptFilesWizard *) parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } void initializePage() override { mParent->setLabelText(QString()); } private: SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { readConfig(); const bool de_vs = Kleo::gnupgUsesDeVsCompliance(); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style ugliness. setWizardStyle(QWizard::ModernStyle); #endif mSigEncPage = new SigEncPage(this); mResultPage = new ResultPage(this); connect(this, &QWizard::currentIdChanged, this, &SignEncryptFilesWizard::slotCurrentIdChanged); setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) | QWizard::NoBackButtonOnLastPage | QWizard::NoBackButtonOnStartPage); if (de_vs) { /* We use a custom button to display a label next to the buttons. */ auto btn = button(QWizard::CustomButton1); /* We style the button so that it looks and acts like a label. */ btn->setStyleSheet(QStringLiteral("border: none")); btn->setFocusPolicy(Qt::NoFocus); } } void SignEncryptFilesWizard::setLabelText(const QString &label) { button(QWizard::CommitButton)->setToolTip(label); setButtonText(QWizard::CustomButton1, label); } void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { Q_EMIT operationPrepared(); } } SignEncryptFilesWizard::~SignEncryptFilesWizard() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void SignEncryptFilesWizard::setSigningPreset(bool preset) { mSigEncPage->setSigningPreset(preset); } void SignEncryptFilesWizard::setSigningUserMutable(bool mut) { if (mut == mSigningUserMutable) { return; } mSigningUserMutable = mut; } void SignEncryptFilesWizard::setEncryptionPreset(bool preset) { mSigEncPage->setEncryptionPreset(preset); } void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut) { if (mut == mEncryptionUserMutable) { return; } mEncryptionUserMutable = mut; } void SignEncryptFilesWizard::setArchiveForced(bool archive) { mSigEncPage->setArchiveForced(archive); } void SignEncryptFilesWizard::setArchiveMutable(bool archive) { mSigEncPage->setArchiveMutable(archive); } void SignEncryptFilesWizard::setSingleFile(bool singleFile) { mSigEncPage->setSingleFile(singleFile); } std::vector SignEncryptFilesWizard::resolvedRecipients() const { return mSigEncPage->recipients(); } std::vector SignEncryptFilesWizard::resolvedSigners() const { return mSigEncPage->signers(); } void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr &coll) { mResultPage->setTaskCollection(coll); } void SignEncryptFilesWizard::setOutputNames(const QMap &map) const { mSigEncPage->setOutputNames(map); } QMap SignEncryptFilesWizard::outputNames() const { return mSigEncPage->outputNames(); } bool SignEncryptFilesWizard::encryptSymmetric() const { return mSigEncPage->encryptSymmetric(); } void SignEncryptFilesWizard::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void SignEncryptFilesWizard::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } #include "signencryptfileswizard.moc" diff --git a/src/crypto/gui/verifychecksumsdialog.cpp b/src/crypto/gui/verifychecksumsdialog.cpp index 374c78edd..4c4c02953 100644 --- a/src/crypto/gui/verifychecksumsdialog.cpp +++ b/src/crypto/gui/verifychecksumsdialog.cpp @@ -1,394 +1,396 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/verifychecksumsdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "verifychecksumsdialog.h" #ifndef QT_NO_DIRMODEL +#include + #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; namespace { static Qt::GlobalColor statusColor[] = { Qt::color0, // Unknown - nothing Qt::green, // OK Qt::red, // Failed Qt::darkRed, // Error }; static_assert((sizeof(statusColor) / sizeof(*statusColor)) == VerifyChecksumsDialog::NumStatii, ""); class ColorizedFileSystemModel : public QFileSystemModel { Q_OBJECT public: explicit ColorizedFileSystemModel(QObject *parent = nullptr) : QFileSystemModel(parent) , statusMap() { } QVariant data(const QModelIndex &mi, int role = Qt::DisplayRole) const override { - if (mi.isValid() && role == Qt::BackgroundRole) { + if (mi.isValid() && role == Qt::BackgroundRole && !SystemInfo::isHighContrastModeActive()) { const QHash::const_iterator it = statusMap.find(filePath(mi)); if (it != statusMap.end()) if (const Qt::GlobalColor c = statusColor[*it]) { return QColor(c); } } return QFileSystemModel::data(mi, role); } public Q_SLOTS: void setStatus(const QString &file, VerifyChecksumsDialog::Status status) { if (status >= VerifyChecksumsDialog::NumStatii || file.isEmpty()) { return; } // canonicalize filename: const QModelIndex mi = index(file); const QString canonical = filePath(mi); if (canonical.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "can't locate file " << file; return; } const QHash::iterator it = statusMap.find(canonical); if (it != statusMap.end()) if (*it == status) { return; // nothing to do } else { *it = status; } else { statusMap[canonical] = status; } emitDataChangedFor(mi); } void clearStatusInformation() { using std::swap; QHash oldStatusMap; swap(statusMap, oldStatusMap); for (QHash::const_iterator it = oldStatusMap.constBegin(), end = oldStatusMap.constEnd(); it != end; ++it) { emitDataChangedFor(it.key()); } } private: void emitDataChangedFor(const QString &file) { emitDataChangedFor(index(file)); } void emitDataChangedFor(const QModelIndex &mi) { const QModelIndex p = parent(mi); Q_EMIT dataChanged(index(mi.row(), 0, p), index(mi.row(), columnCount(p) - 1, p)); } private: QHash statusMap; }; static int find_layout_item(const QBoxLayout &blay) { for (int i = 0, end = blay.count(); i < end; ++i) if (QLayoutItem *item = blay.itemAt(i)) if (item->layout()) { return i; } return 0; } struct BaseWidget { QSortFilterProxyModel proxy; QLabel label; QTreeView view; BaseWidget(QFileSystemModel *model, QWidget *parent, QVBoxLayout *vlay) : proxy() , label(parent) , view(parent) { KDAB_SET_OBJECT_NAME(proxy); KDAB_SET_OBJECT_NAME(label); KDAB_SET_OBJECT_NAME(view); const int row = find_layout_item(*vlay); vlay->insertWidget(row, &label); vlay->insertWidget(row + 1, &view, 1); proxy.setSourceModel(model); view.setModel(&proxy); QRect r; for (int i = 0; i < proxy.columnCount(); ++i) { view.resizeColumnToContents(i); } // define some minimum sizes view.header()->resizeSection(0, qMax(view.header()->sectionSize(0), 220)); view.header()->resizeSection(1, qMax(view.header()->sectionSize(1), 75)); view.header()->resizeSection(2, qMax(view.header()->sectionSize(2), 75)); view.header()->resizeSection(3, qMax(view.header()->sectionSize(3), 140)); for (int i = 0; i < proxy.rowCount(); ++i) { r = r.united(view.visualRect(proxy.index(proxy.columnCount() - 1, i))); } view.setMinimumSize(QSize(qBound(r.width() + 4 * view.frameWidth(), 220 + 75 + 75 + 140 + 4 * view.frameWidth(), 1024), // 100 is the default defaultSectionSize qBound(r.height(), 220, 512))); } void setBase(const QString &base) { label.setText(base); if (auto fsm = qobject_cast(proxy.sourceModel())) { view.setRootIndex(proxy.mapFromSource(fsm->index(base))); } else { qCWarning(KLEOPATRA_LOG) << "expect a QFileSystemModel-derived class as proxy.sourceModel(), got "; if (!proxy.sourceModel()) { qCWarning(KLEOPATRA_LOG) << "a null pointer"; } else { qCWarning(KLEOPATRA_LOG) << proxy.sourceModel()->metaObject()->className(); } } } }; } // anon namespace class VerifyChecksumsDialog::Private { friend class ::Kleo::Crypto::Gui::VerifyChecksumsDialog; VerifyChecksumsDialog *const q; public: explicit Private(VerifyChecksumsDialog *qq) : q(qq), bases(), errors(), model(), ui(q) { qRegisterMetaType("Kleo::Crypto::Gui::VerifyChecksumsDialog::Status"); } private: void slotErrorButtonClicked() { KMessageBox::errorList(q, i18n("The following errors and warnings were recorded:"), errors, i18n("Checksum Verification Errors")); } private: void updateErrors() { const bool active = ui.isProgressBarActive(); ui.progressLabel.setVisible(active); ui.progressBar. setVisible(active); ui.errorLabel. setVisible(!active); ui.errorButton. setVisible(!active && !errors.empty()); if (errors.empty()) { ui.errorLabel.setText(i18n("No errors occurred")); } else { ui.errorLabel.setText(i18np("One error occurred", "%1 errors occurred", errors.size())); } } private: QStringList bases; QStringList errors; ColorizedFileSystemModel model; struct UI { std::vector baseWidgets; QLabel progressLabel; QProgressBar progressBar; QLabel errorLabel; QPushButton errorButton; QDialogButtonBox buttonBox; QVBoxLayout vlay; QHBoxLayout hlay[2]; explicit UI(VerifyChecksumsDialog *q) : baseWidgets(), progressLabel(i18n("Progress:"), q), progressBar(q), errorLabel(i18n("No errors occurred"), q), errorButton(i18nc("Show Errors", "Show"), q), buttonBox(QDialogButtonBox::Close, Qt::Horizontal, q), vlay(q) { KDAB_SET_OBJECT_NAME(progressLabel); KDAB_SET_OBJECT_NAME(progressBar); KDAB_SET_OBJECT_NAME(errorLabel); KDAB_SET_OBJECT_NAME(errorButton); KDAB_SET_OBJECT_NAME(buttonBox); KDAB_SET_OBJECT_NAME(vlay); KDAB_SET_OBJECT_NAME(hlay[0]); KDAB_SET_OBJECT_NAME(hlay[1]); errorButton.setAutoDefault(false); hlay[0].addWidget(&progressLabel); hlay[0].addWidget(&progressBar, 1); hlay[1].addWidget(&errorLabel, 1); hlay[1].addWidget(&errorButton); vlay.addLayout(&hlay[0]); vlay.addLayout(&hlay[1]); vlay.addWidget(&buttonBox); errorLabel.hide(); errorButton.hide(); QPushButton *close = closeButton(); connect(close, &QPushButton::clicked, q, &VerifyChecksumsDialog::canceled); connect(close, &QPushButton::clicked, q, &VerifyChecksumsDialog::accept); connect(&errorButton, SIGNAL(clicked()), q, SLOT(slotErrorButtonClicked())); } ~UI() { qDeleteAll(baseWidgets); } QPushButton *closeButton() const { return buttonBox.button(QDialogButtonBox::Close); } void setBases(const QStringList &bases, QFileSystemModel *model) { // create new BaseWidgets: for (unsigned int i = baseWidgets.size(), end = bases.size(); i < end; ++i) { baseWidgets.push_back(new BaseWidget(model, vlay.parentWidget(), &vlay)); } // shed surplus BaseWidgets: for (unsigned int i = bases.size(), end = baseWidgets.size(); i < end; ++i) { delete baseWidgets.back(); baseWidgets.pop_back(); } Q_ASSERT(static_cast(bases.size()) == baseWidgets.size()); // update bases: for (unsigned int i = 0; i < baseWidgets.size(); ++i) { baseWidgets[i]->setBase(bases[i]); } } void setProgress(int cur, int tot) { progressBar.setMaximum(tot); progressBar.setValue(cur); } bool isProgressBarActive() const { const int tot = progressBar.maximum(); const int cur = progressBar.value(); return !tot || cur != tot; } } ui; }; VerifyChecksumsDialog::VerifyChecksumsDialog(QWidget *parent) : QDialog(parent), d(new Private(this)) { } VerifyChecksumsDialog::~VerifyChecksumsDialog() {} // slot void VerifyChecksumsDialog::setBaseDirectories(const QStringList &bases) { if (d->bases == bases) { return; } d->bases = bases; d->ui.setBases(bases, &d->model); } // slot void VerifyChecksumsDialog::setErrors(const QStringList &errors) { if (d->errors == errors) { return; } d->errors = errors; d->updateErrors(); } // slot void VerifyChecksumsDialog::setProgress(int cur, int tot) { d->ui.setProgress(cur, tot); d->updateErrors(); } // slot void VerifyChecksumsDialog::setStatus(const QString &file, Status status) { d->model.setStatus(file, status); } // slot void VerifyChecksumsDialog::clearStatusInformation() { d->errors.clear(); d->updateErrors(); d->model.clearStatusInformation(); } #include "verifychecksumsdialog.moc" #include "moc_verifychecksumsdialog.cpp" #endif // QT_NO_DIRMODEL diff --git a/src/dialogs/selftestdialog.cpp b/src/dialogs/selftestdialog.cpp index d8915a7a9..93576f728 100644 --- a/src/dialogs/selftestdialog.cpp +++ b/src/dialogs/selftestdialog.cpp @@ -1,359 +1,363 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/selftestdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "selftestdialog.h" #include "ui_selftestdialog.h" #include +#include + #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Dialogs; namespace { class Model : public QAbstractTableModel { Q_OBJECT public: explicit Model(QObject *parent = nullptr) : QAbstractTableModel(parent), m_tests() { } enum Column { TestName, TestResult, NumColumns }; const std::shared_ptr &fromModelIndex(const QModelIndex &idx) const { const unsigned int row = idx.row(); if (row < m_tests.size()) { return m_tests[row]; } static const std::shared_ptr null; return null; } int rowCount(const QModelIndex &idx) const override { return idx.isValid() ? 0 : m_tests.size(); } int columnCount(const QModelIndex &) const override { return NumColumns; } QVariant data(const QModelIndex &idx, int role) const override { const unsigned int row = idx.row(); if (idx.isValid() && row < m_tests.size()) switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: switch (idx.column()) { case TestName: return m_tests[row]->name(); case TestResult: return m_tests[row]->skipped() ? i18n("Skipped") : m_tests[row]->passed() ? i18n("Passed") : /* else */ m_tests[row]->shortError(); } break; case Qt::BackgroundRole: - KColorScheme scheme(qApp->palette().currentColorGroup()); - return (m_tests[row]->skipped() ? scheme.background(KColorScheme::NeutralBackground) : - m_tests[row]->passed() ? scheme.background(KColorScheme::PositiveBackground) : - scheme.background(KColorScheme::NegativeBackground)).color(); + if (!SystemInfo::isHighContrastModeActive()) { + KColorScheme scheme(qApp->palette().currentColorGroup()); + return (m_tests[row]->skipped() ? scheme.background(KColorScheme::NeutralBackground) : + m_tests[row]->passed() ? scheme.background(KColorScheme::PositiveBackground) : + scheme.background(KColorScheme::NegativeBackground)).color(); + } } return QVariant(); } QVariant headerData(int section, Qt::Orientation o, int role) const override { if (o == Qt::Horizontal && section >= 0 && section < NumColumns && role == Qt::DisplayRole) switch (section) { case TestName: return i18n("Test Name"); case TestResult: return i18n("Result"); } return QVariant(); } void clear() { if (m_tests.empty()) { return; } beginRemoveRows(QModelIndex(), 0, m_tests.size() - 1); m_tests.clear(); endRemoveRows(); } void append(const std::vector> &tests) { if (tests.empty()) { return; } beginInsertRows(QModelIndex(), m_tests.size(), m_tests.size() + tests.size()); m_tests.insert(m_tests.end(), tests.begin(), tests.end()); endInsertRows(); } void reloadData() { if (!m_tests.empty()) { Q_EMIT dataChanged(index(0, 0), index(m_tests.size() - 1, NumColumns - 1)); } } const std::shared_ptr &at(unsigned int idx) const { return m_tests.at(idx); } private: std::vector> m_tests; }; class Proxy : public QSortFilterProxyModel { Q_OBJECT public: explicit Proxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent), m_showAll(true) { setDynamicSortFilter(true); } bool showAll() const { return m_showAll; } Q_SIGNALS: void showAllChanged(bool); public Q_SLOTS: void setShowAll(bool on) { if (on == m_showAll) { return; } m_showAll = on; invalidateFilter(); Q_EMIT showAllChanged(on); } private: bool filterAcceptsRow(int src_row, const QModelIndex &src_parent) const override { if (m_showAll) { return true; } if (const Model *const model = qobject_cast(sourceModel())) { if (!src_parent.isValid() && src_row >= 0 && src_row < model->rowCount(src_parent)) { if (const std::shared_ptr &t = model->at(src_row)) { return !t->passed(); } else { qCWarning(KLEOPATRA_LOG) << "NULL test??"; } } else { if (src_parent.isValid()) { qCWarning(KLEOPATRA_LOG) << "view asks for subitems!"; } else { qCWarning(KLEOPATRA_LOG) << "index " << src_row << " is out of range [" << 0 << "," << model->rowCount(src_parent) << "]"; } } } else { qCWarning(KLEOPATRA_LOG) << "expected a ::Model, got "; if (!sourceModel()) { qCWarning(KLEOPATRA_LOG) << "a null pointer"; } else { qCWarning(KLEOPATRA_LOG) << sourceModel()->metaObject()->className(); } } return false; } private: bool m_showAll; }; } class SelfTestDialog::Private { friend class ::Kleo::Dialogs::SelfTestDialog; SelfTestDialog *const q; public: explicit Private(SelfTestDialog *qq) : q(qq), model(q), proxy(q), ui(q) { proxy.setSourceModel(&model); ui.resultsTV->setModel(&proxy); ui.detailsGB->hide(); ui.proposedCorrectiveActionGB->hide(); connect(ui.resultsTV->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged())); connect(ui.showAllCB, &QAbstractButton::toggled, &proxy, &Proxy::setShowAll); } private: void slotSelectionChanged() { const int row = selectedRowIndex(); if (row < 0) { ui.detailsLB->setText(i18n("(select test first)")); ui.detailsGB->hide(); ui.proposedCorrectiveActionGB->hide(); } else { const std::shared_ptr &t = model.at(row); ui.detailsLB->setText(t->longError()); ui.detailsGB->setVisible(!t->passed()); const QString action = t->proposedFix(); ui.proposedCorrectiveActionGB->setVisible(!t->passed() && !action.isEmpty()); ui.proposedCorrectiveActionLB->setText(action); ui.doItPB->setVisible(!t->passed() && t->canFixAutomatically()); } } void slotDoItClicked() { if (const std::shared_ptr st = model.fromModelIndex(selectedRow())) if (st->fix()) { model.reloadData(); } } private: void updateColumnSizes() { ui.resultsTV->header()->resizeSections(QHeaderView::ResizeToContents); } private: QModelIndex selectedRow() const { const QItemSelectionModel *const ism = ui.resultsTV->selectionModel(); if (!ism) { return QModelIndex(); } const QModelIndexList mil = ism->selectedRows(); return mil.empty() ? QModelIndex() : proxy.mapToSource(mil.front()); } int selectedRowIndex() const { return selectedRow().row(); } private: Model model; Proxy proxy; struct UI : public Ui_SelfTestDialog { QPushButton *rerunPB; explicit UI(SelfTestDialog *qq) : Ui_SelfTestDialog(), rerunPB(new QPushButton(i18n("Rerun Tests"))) { setupUi(qq); buttonBox->addButton(rerunPB, QDialogButtonBox::ActionRole); buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Continue")); connect(rerunPB, &QAbstractButton::clicked, qq, &SelfTestDialog::updateRequested); } } ui; }; SelfTestDialog::SelfTestDialog(QWidget *p, Qt::WindowFlags f) : QDialog(p, f), d(new Private(this)) { setAutomaticMode(false); } SelfTestDialog::SelfTestDialog(const std::vector> &tests, QWidget *p, Qt::WindowFlags f) : QDialog(p, f), d(new Private(this)) { addSelfTests(tests); setAutomaticMode(false); } SelfTestDialog::~SelfTestDialog() {} void SelfTestDialog::clear() { d->model.clear(); } void SelfTestDialog::addSelfTest(const std::shared_ptr &test) { d->model.append(std::vector>(1, test)); d->updateColumnSizes(); } void SelfTestDialog::addSelfTests(const std::vector> &tests) { d->model.append(tests); d->updateColumnSizes(); } void SelfTestDialog::setRunAtStartUp(bool on) { d->ui.runAtStartUpCB->setChecked(on); } bool SelfTestDialog::runAtStartUp() const { return d->ui.runAtStartUpCB->isChecked(); } void SelfTestDialog::setAutomaticMode(bool automatic) { d->ui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(automatic); d->ui.buttonBox->button(QDialogButtonBox::Cancel)->setVisible(automatic); d->ui.buttonBox->button(QDialogButtonBox::Close)->setVisible(!automatic); } #include "selftestdialog.moc" #include "moc_selftestdialog.cpp" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 343ff3284..4bea556e9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,779 +1,782 @@ /* -*- mode: c++; c-basic-offset:4 -*- mainwindow.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "mainwindow.h" #include "aboutdata.h" #include "settings.h" #include #include "view/padwidget.h" #include "view/searchbar.h" #include "view/tabwidget.h" #include "view/keylistcontroller.h" #include "view/keycacheoverlay.h" #include "view/smartcardwidget.h" #include "view/welcomewidget.h" #include "commands/selftestcommand.h" #include "commands/importcrlcommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/signencryptfilescommand.h" #include "conf/groupsconfigdialog.h" #include "utils/detail_p.h" #include #include "utils/action_data.h" #include "utils/filedialog.h" #include "utils/clipboardmenu.h" #include "utils/gui-helper.h" #include "utils/qt-cxx20-compat.h" #include "dialogs/updatenotification.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include using namespace std::chrono_literals; using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; static KGuiItem KStandardGuiItem_quit() { static const QString app = KAboutData::applicationData().displayName(); KGuiItem item = KStandardGuiItem::quit(); item.setText(xi18nc("@action:button", "&Quit %1", app)); return item; } static KGuiItem KStandardGuiItem_close() { KGuiItem item = KStandardGuiItem::close(); item.setText(i18nc("@action:button", "Only &Close Window")); return item; } static bool isQuitting = false; namespace { static const std::vector mainViewActionNames = { QStringLiteral("view_certificate_overview"), QStringLiteral("manage_smartcard"), QStringLiteral("pad_view") }; class CertificateView : public QWidget, public FocusFirstChild { Q_OBJECT public: CertificateView(QWidget* parent = nullptr) : QWidget{parent} , ui{this} { } SearchBar *searchBar() const { return ui.searchBar; } TabWidget *tabWidget() const { return ui.tabWidget; } void focusFirstChild(Qt::FocusReason reason) override { ui.searchBar->lineEdit()->setFocus(reason); } private: struct UI { TabWidget *tabWidget = nullptr; SearchBar *searchBar = nullptr; explicit UI(CertificateView *q) { auto vbox = new QVBoxLayout{q}; vbox->setSpacing(0); searchBar = new SearchBar{q}; vbox->addWidget(searchBar); tabWidget = new TabWidget{q}; vbox->addWidget(tabWidget); tabWidget->connectSearchBar(searchBar); } } ui; }; } class MainWindow::Private { friend class ::MainWindow; MainWindow *const q; public: explicit Private(MainWindow *qq); ~Private(); template void createAndStart() { (new T(this->currentView(), &this->controller))->start(); } template void createAndStart(QAbstractItemView *view) { (new T(view, &this->controller))->start(); } template void createAndStart(const QStringList &a) { (new T(a, this->currentView(), &this->controller))->start(); } template void createAndStart(const QStringList &a, QAbstractItemView *view) { (new T(a, view, &this->controller))->start(); } void closeAndQuit() { const QString app = KAboutData::applicationData().displayName(); const int rc = KMessageBox::questionYesNoCancel(q, xi18n("%1 may be used by other applications as a service." "You may instead want to close this window without exiting %1.", app), i18nc("@title:window", "Really Quit?"), KStandardGuiItem_close(), KStandardGuiItem_quit(), KStandardGuiItem::cancel(), QLatin1String("really-quit-") + app.toLower()); if (rc == KMessageBox::Cancel) { return; } isQuitting = true; if (!q->close()) { return; } // WARNING: 'this' might be deleted at this point! if (rc == KMessageBox::No) { qApp->quit(); } } void configureToolbars() { KEditToolBar dlg(q->factory()); dlg.exec(); } void editKeybindings() { KShortcutsDialog::showDialog(q->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, q); updateSearchBarClickMessage(); } void updateSearchBarClickMessage() { const QString shortcutStr = focusToClickSearchAction->shortcut().toString(); ui.searchTab->searchBar()->updateClickMessage(shortcutStr); } void updateStatusBar() { if (Kleo::gnupgUsesDeVsCompliance()) { auto statusBar = std::make_unique(); auto statusLbl = std::make_unique(Formatting::deVsString(Kleo::gnupgIsDeVsCompliant())); - const auto color = KColorScheme(QPalette::Active, KColorScheme::View).foreground( - Kleo::gnupgIsDeVsCompliant() ? KColorScheme::NormalText: KColorScheme::NegativeText - ).color(); - const auto background = KColorScheme(QPalette::Active, KColorScheme::View).background( - Kleo::gnupgIsDeVsCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground - ).color(); - statusLbl->setStyleSheet(QStringLiteral("QLabel { color: %1; background-color: %2; }"). - arg(color.name()).arg(background.name())); + if (!SystemInfo::isHighContrastModeActive()) { + const auto color = KColorScheme(QPalette::Active, KColorScheme::View).foreground( + Kleo::gnupgIsDeVsCompliant() ? KColorScheme::NormalText: KColorScheme::NegativeText + ).color(); + const auto background = KColorScheme(QPalette::Active, KColorScheme::View).background( + Kleo::gnupgIsDeVsCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground + ).color(); + statusLbl->setStyleSheet(QStringLiteral("QLabel { color: %1; background-color: %2; }"). + arg(color.name()).arg(background.name())); + } statusBar->insertPermanentWidget(0, statusLbl.release()); q->setStatusBar(statusBar.release()); // QMainWindow takes ownership } else { q->setStatusBar(nullptr); } } void selfTest() { createAndStart(); } void configureGroups() { if (KConfigDialog::showDialog(GroupsConfigDialog::dialogName())) { return; } KConfigDialog *dialog = new GroupsConfigDialog(q); dialog->show(); } void showHandbook(); void gnupgLogViewer() { const QString exec = QStandardPaths::findExecutable(QStringLiteral("kwatchgnupg")); if (exec.isEmpty()) { KMessageBox::error( q, i18n("Could not start the GnuPG Log Viewer (kwatchgnupg). " "Please check your installation."), i18n("Error Starting KWatchGnuPG")); } else { QProcess::startDetached(QStringLiteral("kwatchgnupg"), QStringList()); } } void forceUpdateCheck() { UpdateNotification::forceUpdateCheck(q); } void slotConfigCommitted(); void slotContextMenuRequested(QAbstractItemView *, const QPoint &p) { if (auto const menu = qobject_cast(q->factory()->container(QStringLiteral("listview_popup"), q))) { menu->exec(p); } else { qCDebug(KLEOPATRA_LOG) << "no \"listview_popup\" in kleopatra's ui.rc file"; } } void slotFocusQuickSearch() { ui.searchTab->searchBar()->lineEdit()->setFocus(); } void showView(const QString &actionName, QWidget *widget) { const auto coll = q->actionCollection(); if (coll) { for ( const QString &name : mainViewActionNames ) { if (auto action = coll->action(name)) { action->setChecked(name == actionName); } } } ui.stackWidget->setCurrentWidget(widget); if (auto ffci = dynamic_cast(widget)) { ffci->focusFirstChild(Qt::TabFocusReason); } } void showCertificateView() { if (KeyCache::instance()->keys().empty()) { showView(QStringLiteral("view_certificate_overview"), ui.welcomeWidget); } else { showView(QStringLiteral("view_certificate_overview"), ui.searchTab); } } void showSmartcardView() { showView(QStringLiteral("manage_smartcard"), ui.scWidget); } void showPadView() { if (!ui.padWidget) { ui.padWidget = new PadWidget; ui.stackWidget->addWidget(ui.padWidget); } showView(QStringLiteral("pad_view"), ui.padWidget); ui.stackWidget->resize(ui.padWidget->sizeHint()); } void restartDaemons() { Kleo::killDaemons(); } private: void setupActions(); QAbstractItemView *currentView() const { return ui.searchTab->tabWidget()->currentView(); } void keyListingDone() { const auto curWidget = ui.stackWidget->currentWidget(); if (curWidget == ui.scWidget || curWidget == ui.padWidget) { return; } showCertificateView(); } private: Kleo::KeyListController controller; bool firstShow : 1; struct UI { CertificateView *searchTab = nullptr; PadWidget *padWidget = nullptr; SmartCardWidget *scWidget = nullptr; WelcomeWidget *welcomeWidget = nullptr; QStackedWidget *stackWidget = nullptr; explicit UI(MainWindow *q); } ui; QAction *focusToClickSearchAction = nullptr; ClipboardMenu *clipboadMenu = nullptr; }; MainWindow::Private::UI::UI(MainWindow *q) : padWidget(nullptr) { auto mainWidget = new QWidget{q}; auto mainLayout = new QVBoxLayout(mainWidget); stackWidget = new QStackedWidget{q}; searchTab = new CertificateView{q}; stackWidget->addWidget(searchTab); new KeyCacheOverlay(mainWidget, q); scWidget = new SmartCardWidget{q}; stackWidget->addWidget(scWidget); welcomeWidget = new WelcomeWidget{q}; stackWidget->addWidget(welcomeWidget); mainLayout->addWidget(stackWidget); q->setCentralWidget(mainWidget); } MainWindow::Private::Private(MainWindow *qq) : q(qq), controller(q), firstShow(true), ui(q) { KDAB_SET_OBJECT_NAME(controller); AbstractKeyListModel *flatModel = AbstractKeyListModel::createFlatKeyListModel(q); AbstractKeyListModel *hierarchicalModel = AbstractKeyListModel::createHierarchicalKeyListModel(q); KDAB_SET_OBJECT_NAME(flatModel); KDAB_SET_OBJECT_NAME(hierarchicalModel); controller.setFlatModel(flatModel); controller.setHierarchicalModel(hierarchicalModel); controller.setTabWidget(ui.searchTab->tabWidget()); ui.searchTab->tabWidget()->setFlatModel(flatModel); ui.searchTab->tabWidget()->setHierarchicalModel(hierarchicalModel); setupActions(); ui.stackWidget->setCurrentWidget(ui.searchTab); if (auto action = q->actionCollection()->action(QStringLiteral("view_certificate_overview"))) { action->setChecked(true); } connect(&controller, SIGNAL(contextMenuRequested(QAbstractItemView*,QPoint)), q, SLOT(slotContextMenuRequested(QAbstractItemView*,QPoint))); connect(KeyCache::instance().get(), &KeyCache::keyListingDone, q, [this] () {keyListingDone();}); q->createGUI(QStringLiteral("kleopatra.rc")); // make toolbar buttons accessible by keyboard if (auto toolbar = q->findChild()) { auto toolbarButtons = toolbar->findChildren(); for (auto b : toolbarButtons) { b->setFocusPolicy(Qt::TabFocus); } // move toolbar and its child widgets before the central widget in the tab order; // this is necessary to make Shift+Tab work as expected forceSetTabOrder(q, toolbar); auto toolbarChildren = toolbar->findChildren(); std::for_each(std::rbegin(toolbarChildren), std::rend(toolbarChildren), [toolbar](auto w) { forceSetTabOrder(toolbar, w); }); } q->setAcceptDrops(true); // set default window size q->resize(QSize(1024, 500)); q->setAutoSaveSettings(); updateSearchBarClickMessage(); updateStatusBar(); if (KeyCache::instance()->initialized()) { keyListingDone(); } } MainWindow::Private::~Private() {} MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : KXmlGuiWindow(parent, flags), d(new Private(this)) {} MainWindow::~MainWindow() {} void MainWindow::Private::setupActions() { KActionCollection *const coll = q->actionCollection(); const std::vector action_data = { // see keylistcontroller.cpp for more actions // Tools menu #ifndef Q_OS_WIN { "tools_start_kwatchgnupg", i18n("GnuPG Log Viewer"), QString(), "kwatchgnupg", q, [this](bool) { gnupgLogViewer(); }, QString() }, #endif { "tools_restart_backend", i18nc("@action:inmenu", "Restart Background Processes"), i18nc("@info:tooltip", "Restart the background processes, e.g. after making changes to the configuration."), "view-refresh", q, [this](bool) { restartDaemons(); }, {} }, // Help menu #ifdef Q_OS_WIN { "help_check_updates", i18n("Check for updates"), QString(), "gpg4win-compact", q, [this](bool) { forceUpdateCheck(); }, QString() }, #endif // View menu { "view_certificate_overview", i18nc("@action show certificate overview", "Certificates"), i18n("Show certificate overview"), "view-certificate", q, [this](bool) { showCertificateView(); }, QString() }, { "pad_view", i18nc("@action show input / output area for encrypting/signing resp. decrypting/verifying text", "Notepad"), i18n("Show pad for encrypting/decrypting and signing/verifying text"), "note", q, [this](bool) { showPadView(); }, QString() }, { "manage_smartcard", i18nc("@action show smartcard management view", "Smartcards"), i18n("Show smartcard management"), "auth-sim-locked", q, [this](bool) { showSmartcardView(); }, QString() }, // Settings menu { "settings_self_test", i18n("Perform Self-Test"), QString(), nullptr, q, [this](bool) { selfTest(); }, QString() }, { "configure_groups", i18n("Configure Groups..."), QString(), "group", q, [this](bool) { configureGroups(); }, QString() } }; make_actions_from_data(action_data, coll); if (!Settings().groupsEnabled()) { if (auto action = coll->action(QStringLiteral("configure_groups"))) { delete action; } } for ( const QString &name : mainViewActionNames ) { if (auto action = coll->action(name)) { action->setCheckable(true); } } KStandardAction::close(q, SLOT(close()), coll); KStandardAction::quit(q, SLOT(closeAndQuit()), coll); KStandardAction::configureToolbars(q, SLOT(configureToolbars()), coll); KStandardAction::keyBindings(q, SLOT(editKeybindings()), coll); KStandardAction::preferences(qApp, SLOT(openOrRaiseConfigDialog()), coll); focusToClickSearchAction = new QAction(i18n("Set Focus to Quick Search"), q); coll->addAction(QStringLiteral("focus_to_quickseach"), focusToClickSearchAction); coll->setDefaultShortcut(focusToClickSearchAction, QKeySequence(Qt::ALT | Qt::Key_Q)); connect(focusToClickSearchAction, SIGNAL(triggered(bool)), q, SLOT(slotFocusQuickSearch())); clipboadMenu = new ClipboardMenu(q); clipboadMenu->setMainWindow(q); clipboadMenu->clipboardMenu()->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); clipboadMenu->clipboardMenu()->setPopupMode(QToolButton::InstantPopup); coll->addAction(QStringLiteral("clipboard_menu"), clipboadMenu->clipboardMenu()); /* Add additional help actions for documentation */ const auto compendium = new DocAction(QIcon::fromTheme(QStringLiteral("gpg4win-compact")), i18n("Gpg4win Compendium"), i18nc("The Gpg4win compendium is only available" "at this point (24.7.2017) in german and english." "Please check with Gpg4win before translating this filename.", "gpg4win-compendium-en.pdf"), QStringLiteral("../share/gpg4win")); coll->addAction(QStringLiteral("help_doc_compendium"), compendium); /* Documentation centered around the german approved VS-NfD mode for official * RESTRICTED communication. This is only available in some distributions with * the focus on official communications. */ const auto symguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("Password-based encryption"), i18nc("Only available in German and English. Leave to English for other languages.", "handout_symmetric_encryption_gnupg_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_symenc"), symguide); const auto quickguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("Quickguide"), i18nc("Only available in German and English. Leave to English for other languages.", "handout_sign_encrypt_gnupg_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_quickguide"), quickguide); const auto vsa10573 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10573"), i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10573-ENG_secops-20220207.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_vsa10573"), vsa10573); const auto vsa10584 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10584"), i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10584-ENG_secops-20220207.pdf"), QStringLiteral("../share/doc/gnupg-vsd")); coll->addAction(QStringLiteral("help_doc_vsa10584"), vsa10584); const auto man_gpg = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("GnuPG Manual"), QStringLiteral("gnupg.pdf"), QStringLiteral("../share/doc/gnupg")); coll->addAction(QStringLiteral("help_doc_gnupg"), man_gpg); q->setStandardToolBarMenuEnabled(true); controller.createActions(coll); ui.searchTab->tabWidget()->createActions(coll); } void MainWindow::Private::slotConfigCommitted() { controller.updateConfig(); updateStatusBar(); } void MainWindow::closeEvent(QCloseEvent *e) { // KMainWindow::closeEvent() insists on quitting the application, // so do not let it touch the event... qCDebug(KLEOPATRA_LOG); if (d->controller.hasRunningCommands()) { if (d->controller.shutdownWarningRequired()) { const int ret = KMessageBox::warningContinueCancel(this, i18n("There are still some background operations ongoing. " "These will be terminated when closing the window. " "Proceed?"), i18n("Ongoing Background Tasks")); if (ret != KMessageBox::Continue) { e->ignore(); return; } } d->controller.cancelCommands(); if (d->controller.hasRunningCommands()) { // wait for them to be finished: setEnabled(false); QEventLoop ev; QTimer::singleShot(100ms, &ev, &QEventLoop::quit); connect(&d->controller, &KeyListController::commandsExecuting, &ev, &QEventLoop::quit); ev.exec(); if (d->controller.hasRunningCommands()) qCWarning(KLEOPATRA_LOG) << "controller still has commands running, this may crash now..."; setEnabled(true); } } if (isQuitting || qApp->isSavingSession()) { d->ui.searchTab->tabWidget()->saveViews(KSharedConfig::openConfig().data()); KConfigGroup grp(KConfigGroup(KSharedConfig::openConfig(), autoSaveGroup())); saveMainWindowSettings(grp); e->accept(); } else { e->ignore(); hide(); } } void MainWindow::showEvent(QShowEvent *e) { KXmlGuiWindow::showEvent(e); if (d->firstShow) { d->ui.searchTab->tabWidget()->loadViews(KSharedConfig::openConfig().data()); d->firstShow = false; } if (!savedGeometry.isEmpty()) { restoreGeometry(savedGeometry); } } void MainWindow::hideEvent(QHideEvent *e) { savedGeometry = saveGeometry(); KXmlGuiWindow::hideEvent(e); } void MainWindow::importCertificatesFromFile(const QStringList &files) { if (!files.empty()) { d->createAndStart(files); } } static QStringList extract_local_files(const QMimeData *data) { const QList urls = data->urls(); // begin workaround KDE/Qt misinterpretation of text/uri-list QList::const_iterator end = urls.end(); if (urls.size() > 1 && !urls.back().isValid()) { --end; } // end workaround QStringList result; std::transform(urls.begin(), end, std::back_inserter(result), std::mem_fn(&QUrl::toLocalFile)); result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&QString::isEmpty)), result.end()); return result; } static bool can_decode_local_files(const QMimeData *data) { if (!data) { return false; } return !extract_local_files(data).empty(); } void MainWindow::dragEnterEvent(QDragEnterEvent *e) { qCDebug(KLEOPATRA_LOG); if (can_decode_local_files(e->mimeData())) { e->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent *e) { qCDebug(KLEOPATRA_LOG); if (!can_decode_local_files(e->mimeData())) { return; } e->setDropAction(Qt::CopyAction); const QStringList files = extract_local_files(e->mimeData()); const unsigned int classification = classify(files); QMenu menu; QAction *const signEncrypt = menu.addAction(i18n("Sign/Encrypt...")); QAction *const decryptVerify = mayBeAnyMessageType(classification) ? menu.addAction(i18n("Decrypt/Verify...")) : nullptr; if (signEncrypt || decryptVerify) { menu.addSeparator(); } QAction *const importCerts = mayBeAnyCertStoreType(classification) ? menu.addAction(i18n("Import Certificates")) : nullptr; QAction *const importCRLs = mayBeCertificateRevocationList(classification) ? menu.addAction(i18n("Import CRLs")) : nullptr; if (importCerts || importCRLs) { menu.addSeparator(); } if (!signEncrypt && !decryptVerify && !importCerts && !importCRLs) { return; } menu.addAction(i18n("Cancel")); const QAction *const chosen = menu.exec(mapToGlobal(e->pos())); if (!chosen) { return; } if (chosen == signEncrypt) { d->createAndStart(files); } else if (chosen == decryptVerify) { d->createAndStart(files); } else if (chosen == importCerts) { d->createAndStart(files); } else if (chosen == importCRLs) { d->createAndStart(files); } e->accept(); } void MainWindow::readProperties(const KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::readProperties(cg); setHidden(cg.readEntry("hidden", false)); } void MainWindow::saveProperties(KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::saveProperties(cg); cg.writeEntry("hidden", isHidden()); } #include "mainwindow.moc" #include "moc_mainwindow.cpp" diff --git a/src/view/padwidget.cpp b/src/view/padwidget.cpp index a225815f1..dbae08d6e 100644 --- a/src/view/padwidget.cpp +++ b/src/view/padwidget.cpp @@ -1,543 +1,546 @@ /* -*- mode: c++; c-basic-offset:4 -*- padwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "padwidget.h" #include "kleopatra_debug.h" #include #include #include #include #include +#include #include "crypto/gui/signencryptwidget.h" #include "crypto/gui/resultitemwidget.h" #include "crypto/signencrypttask.h" #include "crypto/decryptverifytask.h" #include #include "utils/input.h" #include "utils/output.h" #include "commands/importcertificatefromdatacommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; static GpgME::Protocol getProtocol(const std::shared_ptr &result) { const auto dvResult = dynamic_cast(result.get()); if (dvResult) { for (const auto &key: KeyCache::instance()->findRecipients(dvResult->decryptionResult())) { return key.protocol(); } for (const auto &key: KeyCache::instance()->findSigners(dvResult->verificationResult())) { return key.protocol(); } } return GpgME::UnknownProtocol; } class PadWidget::Private { friend class ::Kleo::PadWidget; public: Private(PadWidget *qq): q(qq), mEdit(new QTextEdit), mCryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-sign-encrypt")), i18n("Sign / Encrypt Notepad"))), mDecryptBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit-decrypt-verify")), i18n("Decrypt / Verify Notepad"))), mRevertBtn(new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Revert"))), mMessageWidget{new KMessageWidget}, mAdditionalInfoLabel(new QLabel), mSigEncWidget(new SignEncryptWidget(nullptr, true)), mProgressBar(new QProgressBar), mProgressLabel(new QLabel), mLastResultWidget(nullptr), mPGPRB(nullptr), mCMSRB(nullptr), mImportProto(GpgME::UnknownProtocol) { auto vLay = new QVBoxLayout(q); auto btnLay = new QHBoxLayout; vLay->addLayout(btnLay); btnLay->addWidget(mCryptBtn); btnLay->addWidget(mDecryptBtn); btnLay->addWidget(mRevertBtn); mRevertBtn->setVisible(false); btnLay->addWidget(mAdditionalInfoLabel); btnLay->addStretch(-1); mMessageWidget->setMessageType(KMessageWidget::Warning); mMessageWidget->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q)); mMessageWidget->setText(i18n("Signing and encryption is not possible.")); mMessageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "You cannot use Kleopatra for signing or encryption " "because the GnuPG system used by Kleopatra is not %1.", Formatting::deVsString())); mMessageWidget->setCloseButtonVisible(false); mMessageWidget->setVisible(false); vLay->addWidget(mMessageWidget); mProgressBar->setRange(0, 0); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); auto progLay = new QHBoxLayout; progLay->addWidget(mProgressLabel); progLay->addWidget(mProgressBar); mStatusLay = new QVBoxLayout; mStatusLay->addLayout(progLay); vLay->addLayout(mStatusLay, 0); auto tabWidget = new QTabWidget; vLay->addWidget(tabWidget, 1); tabWidget->addTab(mEdit, QIcon::fromTheme(QStringLiteral("edittext")), i18n("Notepad")); // The recipients area auto recipientsWidget = new QWidget; auto recipientsVLay = new QVBoxLayout(recipientsWidget); auto protocolSelectionLay = new QHBoxLayout; bool pgpOnly = KeyCache::instance()->pgpOnly(); if (!pgpOnly) { recipientsVLay->addLayout(protocolSelectionLay); } protocolSelectionLay->addWidget(new QLabel(i18n("

Protocol:

"))); protocolSelectionLay->addStretch(-1); // Once S/MIME is supported add radio for S/MIME here. recipientsVLay->addWidget(mSigEncWidget); tabWidget->addTab(recipientsWidget, QIcon::fromTheme(QStringLiteral("contact-new-symbolic")), i18n("Recipients")); mEdit->setPlaceholderText(i18n("Enter a message to encrypt or decrypt...")); auto fixedFont = QFont(QStringLiteral("Monospace")); fixedFont.setStyleHint(QFont::TypeWriter); // This does not work well // QFontDatabase::systemFont(QFontDatabase::FixedFont); mEdit->setFont(fixedFont); mEdit->setAcceptRichText(false); mEdit->setMinimumWidth(QFontMetrics(fixedFont).averageCharWidth() * 70); if (KeyCache::instance()->pgpOnly() || !Settings{}.cmsEnabled()) { mSigEncWidget->setProtocol(GpgME::OpenPGP); } else { auto grp = new QButtonGroup(q); auto mPGPRB = new QRadioButton(i18n("OpenPGP")); auto mCMSRB = new QRadioButton(i18n("S/MIME")); grp->addButton(mPGPRB); grp->addButton(mCMSRB); KConfigGroup config(KSharedConfig::openConfig(), "Notepad"); if (config.readEntry("wasCMS", false)) { mCMSRB->setChecked(true); mSigEncWidget->setProtocol(GpgME::CMS); } else { mPGPRB->setChecked(true); mSigEncWidget->setProtocol(GpgME::OpenPGP); } protocolSelectionLay->addWidget(mPGPRB); protocolSelectionLay->addWidget(mCMSRB); connect(mPGPRB, &QRadioButton::toggled, q, [this] (bool value) { if (value) { mSigEncWidget->setProtocol(GpgME::OpenPGP); } }); connect(mCMSRB, &QRadioButton::toggled, q, [this] (bool value) { if (value) { mSigEncWidget->setProtocol(GpgME::CMS); } }); } updateCommitButton(); connect(mEdit, &QTextEdit::textChanged, q, [this] () { updateCommitButton(); }); connect(mCryptBtn, &QPushButton::clicked, q, [this] () { if (mImportProto != GpgME::UnknownProtocol) { doImport(); } else { doEncryptSign(); } }); connect(mSigEncWidget, &SignEncryptWidget::operationChanged, q, [this] (const QString &) { updateCommitButton(); }); connect(mDecryptBtn, &QPushButton::clicked, q, [this] () { doDecryptVerify(); }); connect(mRevertBtn, &QPushButton::clicked, q, [this] () { revert(); }); } void revert() { mEdit->setPlainText(QString::fromUtf8(mInputData)); mRevertBtn->setVisible(false); } void updateRecipientsFromResult(const Kleo::Crypto::DecryptVerifyResult &result) { const auto decResult = result.decryptionResult(); for (const auto &recipient: decResult.recipients()) { if (!recipient.keyID()) { continue; } GpgME::Key key; if (strlen(recipient.keyID()) < 16) { key = KeyCache::instance()->findByShortKeyID(recipient.keyID()); } else { key = KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID()); } if (key.isNull()) { std::vector subids; subids.push_back(std::string(recipient.keyID())); for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) { key = subkey.parent(); break; } } if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Unknown key" << recipient.keyID(); mSigEncWidget->addUnknownRecipient(recipient.keyID()); continue; } bool keyFound = false; for (const auto &existingKey: mSigEncWidget->recipients()) { if (existingKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp (existingKey.primaryFingerprint(), key.primaryFingerprint())) { keyFound = true; break; } } if (!keyFound) { mSigEncWidget->addRecipient(key); } } } void cryptDone(const std::shared_ptr &result) { updateCommitButton(); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); mLastResultWidget = new ResultItemWidget(result); mLastResultWidget->showCloseButton(true); mStatusLay->addWidget(mLastResultWidget); connect(mLastResultWidget, &ResultItemWidget::closeButtonClicked, q, [this] () { removeLastResultItem(); }); // Check result protocol if (mPGPRB) { auto proto = getProtocol(result); if (proto == GpgME::UnknownProtocol) { proto = mPGPRB->isChecked() ? GpgME::OpenPGP : GpgME::CMS; } else if (proto == GpgME::OpenPGP) { mPGPRB->setChecked(true); } else if (proto == GpgME::CMS) { mCMSRB->setChecked(true); } KConfigGroup config(KSharedConfig::openConfig(), "Notepad"); config.writeEntry("wasCMS", proto == GpgME::CMS); } if (result->errorCode()) { if (!result->errorString().isEmpty()) { KMessageBox::error(q, result->errorString(), i18nc("@title", "Error in crypto action")); } return; } mEdit->setPlainText(QString::fromUtf8(mOutputData)); mOutputData.clear(); mRevertBtn->setVisible(true); mDecryptBtn->setEnabled(true); const auto decryptVerifyResult = dynamic_cast(result.get()); if (decryptVerifyResult) { updateRecipientsFromResult(*decryptVerifyResult); } } void doDecryptVerify() { doCryptoCommon(); mSigEncWidget->clearAddedRecipients(); mProgressLabel->setText(i18n("Decrypt / Verify") + QStringLiteral("...")); auto input = Input::createFromByteArray(&mInputData, i18n("Notepad")); auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad")); AbstractDecryptVerifyTask *task; auto classification = input->classification(); if (classification & Class::OpaqueSignature || classification & Class::ClearsignedMessage) { auto verifyTask = new VerifyOpaqueTask(); verifyTask->setInput(input); verifyTask->setOutput(output); task = verifyTask; } else { auto decTask = new DecryptVerifyTask(); decTask->setInput(input); decTask->setOutput(output); task = decTask; } try { task->autodetectProtocolFromInput(); } catch (const Kleo::Exception &e) { KMessageBox::error(q, e.message(), i18nc("@title", "Error in crypto action")); updateCommitButton(); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); return; } connect (task, &Task::result, q, [this, task] (const std::shared_ptr &result) { qCDebug(KLEOPATRA_LOG) << "Decrypt / Verify done. Err:" << result->errorCode(); task->deleteLater(); cryptDone(result); }); task->start(); } void removeLastResultItem() { if (mLastResultWidget) { mStatusLay->removeWidget(mLastResultWidget); delete mLastResultWidget; mLastResultWidget = nullptr; } } void doCryptoCommon() { mCryptBtn->setEnabled(false); mDecryptBtn->setEnabled(false); mProgressBar->setVisible(true); mProgressLabel->setVisible(true); mInputData = mEdit->toPlainText().toUtf8(); removeLastResultItem(); } void doEncryptSign() { if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { KMessageBox::sorry(q->topLevelWidget(), xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "Sorry! You cannot use Kleopatra for signing or encryption " "because the GnuPG system used by Kleopatra is not %1.", Formatting::deVsString())); return; } doCryptoCommon(); mProgressLabel->setText(mSigEncWidget->currentOp() + QStringLiteral("...")); auto input = Input::createFromByteArray(&mInputData, i18n("Notepad")); auto output = Output::createFromByteArray(&mOutputData, i18n("Notepad")); auto task = new SignEncryptTask(); task->setInput(input); task->setOutput(output); const auto sigKey = mSigEncWidget->signKey(); const std::vector recipients = mSigEncWidget->recipients(); const bool encrypt = mSigEncWidget->encryptSymmetric() || !recipients.empty(); const bool sign = !sigKey.isNull(); if (sign) { task->setSign(true); std::vector signVector; signVector.push_back(sigKey); task->setSigners(signVector); } else { task->setSign(false); } task->setEncrypt(encrypt); task->setRecipients(recipients); task->setEncryptSymmetric(mSigEncWidget->encryptSymmetric()); task->setAsciiArmor(true); if (sign && !encrypt && sigKey.protocol() == GpgME::OpenPGP) { task->setClearsign(true); } connect (task, &Task::result, q, [this, task] (const std::shared_ptr &result) { qCDebug(KLEOPATRA_LOG) << "Encrypt / Sign done. Err:" << result->errorCode(); task->deleteLater(); cryptDone(result); }); task->start(); } void doImport() { doCryptoCommon(); mProgressLabel->setText(i18n("Importing...")); auto cmd = new Kleo::ImportCertificateFromDataCommand(mInputData, mImportProto); connect(cmd, &Kleo::ImportCertificatesCommand::finished, q, [this] () { mCryptBtn->setEnabled(true); mDecryptBtn->setEnabled(true); mProgressBar->setVisible(false); mProgressLabel->setVisible(false); updateCommitButton(); mRevertBtn->setVisible(true); mEdit->setPlainText(QString()); }); cmd->start(); } void checkImportProtocol() { QGpgME::QByteArrayDataProvider dp(mEdit->toPlainText().toUtf8()); GpgME::Data data(&dp); auto type = data.type(); if (type == GpgME::Data::PGPKey) { mImportProto = GpgME::OpenPGP; } else if (type == GpgME::Data::X509Cert || type == GpgME::Data::PKCS12) { mImportProto = GpgME::CMS; } else { mImportProto = GpgME::UnknownProtocol; } } void updateCommitButton() { mAdditionalInfoLabel->setVisible(false); checkImportProtocol(); if (mImportProto != GpgME::UnknownProtocol) { mCryptBtn->setText(i18nc("1 is an operation to apply to the notepad. " "Like Sign/Encrypt or just Encrypt.", "%1 Notepad", i18n("Import"))); mCryptBtn->setEnabled(true); return; } if (!mSigEncWidget->currentOp().isEmpty()) { mCryptBtn->setEnabled(true); mCryptBtn->setText(i18nc("1 is an operation to apply to the notepad. " "Like Sign/Encrypt or just Encrypt.", "%1 Notepad", mSigEncWidget->currentOp())); } else { mCryptBtn->setText(i18n("Sign / Encrypt Notepad")); mCryptBtn->setEnabled(false); } if (Kleo::gnupgUsesDeVsCompliance()) { const bool de_vs = Kleo::gnupgIsDeVsCompliant() && mSigEncWidget->isDeVsAndValid(); mCryptBtn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); - mCryptBtn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs - ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() - : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); + if (!SystemInfo::isHighContrastModeActive()) { + mCryptBtn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs + ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() + : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); + } mAdditionalInfoLabel->setText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); mAdditionalInfoLabel->setVisible(true); if (!Kleo::gnupgIsDeVsCompliant()) { mCryptBtn->setEnabled(false); } mMessageWidget->setVisible(!Kleo::gnupgIsDeVsCompliant()); } } private: PadWidget *const q; QTextEdit *mEdit; QPushButton *mCryptBtn; QPushButton *mDecryptBtn; QPushButton *mRevertBtn; KMessageWidget *mMessageWidget; QLabel *mAdditionalInfoLabel; QByteArray mInputData; QByteArray mOutputData; SignEncryptWidget *mSigEncWidget; QProgressBar *mProgressBar; QLabel *mProgressLabel; QVBoxLayout *mStatusLay; ResultItemWidget *mLastResultWidget; QList mAutoAddedKeys; QRadioButton *mPGPRB; QRadioButton *mCMSRB; GpgME::Protocol mImportProto; }; PadWidget::PadWidget(QWidget *parent): QWidget(parent), d(new Private(this)) { } void PadWidget::focusFirstChild(Qt::FocusReason reason) { d->mEdit->setFocus(reason); }