diff --git a/src/commands/revokekeycommand.cpp b/src/commands/revokekeycommand.cpp index 09ab3b0f6..8fc0f6a89 100644 --- a/src/commands/revokekeycommand.cpp +++ b/src/commands/revokekeycommand.cpp @@ -1,246 +1,226 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/revokekeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "dialogs/revokekeydialog.h" #include "revokekeycommand.h" #include #include #include #include "kleopatra_debug.h" #include +#include using namespace Kleo; using namespace GpgME; class RevokeKeyCommand::Private : public Command::Private { friend class ::RevokeKeyCommand; RevokeKeyCommand *q_func() const { return static_cast(q); } public: explicit Private(RevokeKeyCommand *qq, KeyListController *c = nullptr); ~Private() override; void start(); void cancel(); private: void ensureDialogCreated(); void onDialogAccepted(); void onDialogRejected(); std::unique_ptr startJob(); void onJobResult(const Error &err); void showError(const Error &err); private: Key key; QPointer dialog; QPointer job; }; RevokeKeyCommand::Private *RevokeKeyCommand::d_func() { return static_cast(d.get()); } const RevokeKeyCommand::Private *RevokeKeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RevokeKeyCommand::Private::Private(RevokeKeyCommand *qq, KeyListController *c) : Command::Private{qq, c} { } RevokeKeyCommand::Private::~Private() = default; namespace { Key getKey(const std::vector &keys) { if (keys.size() != 1) { qCWarning(KLEOPATRA_LOG) << "Expected exactly one key, but got" << keys.size(); return {}; } const Key key = keys.front(); if (key.protocol() != GpgME::OpenPGP) { qCWarning(KLEOPATRA_LOG) << "Expected OpenPGP key, but got" << Formatting::displayName(key.protocol()) << "key"; return {}; } return key; } } void RevokeKeyCommand::Private::start() { key = getKey(keys()); if (key.isNull()) { finished(); return; } if (key.isRevoked()) { information(i18nc("@info", "This key has already been revoked.")); finished(); return; } ensureDialogCreated(); Q_ASSERT(dialog); dialog->setKey(key); dialog->show(); } void RevokeKeyCommand::Private::cancel() { if (job) { job->slotCancel(); } job.clear(); } void RevokeKeyCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new RevokeKeyDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &QDialog::accepted, q, [this]() { onDialogAccepted(); }); connect(dialog, &QDialog::rejected, q, [this]() { onDialogRejected(); }); } void RevokeKeyCommand::Private::onDialogAccepted() { auto revokeJob = startJob(); if (!revokeJob) { finished(); return; } job = revokeJob.release(); } void RevokeKeyCommand::Private::onDialogRejected() { canceled(); } -namespace -{ -std::vector toStdStrings(const QStringList &l) -{ - std::vector v; - v.reserve(l.size()); - std::transform(std::begin(l), std::end(l), std::back_inserter(v), std::mem_fn(&QString::toStdString)); - return v; -} - -auto descriptionToLines(const QString &description) -{ - std::vector lines; - if (!description.isEmpty()) { - lines = toStdStrings(description.split(QLatin1Char('\n'))); - } - return lines; -} -} - std::unique_ptr RevokeKeyCommand::Private::startJob() { std::unique_ptr revokeJob{QGpgME::openpgp()->revokeKeyJob()}; Q_ASSERT(revokeJob); connect(revokeJob.get(), &QGpgME::RevokeKeyJob::result, q, [this](const GpgME::Error &err) { onJobResult(err); }); connect(revokeJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); - const auto description = descriptionToLines(dialog->description()); - const GpgME::Error err = revokeJob->start(key, dialog->reason(), description); + const GpgME::Error err = revokeJob->start(key, GpgME::RevocationReason::Unspecified, {}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Revoking key...")); return revokeJob; } void RevokeKeyCommand::Private::onJobResult(const Error &err) { if (err) { showError(err); finished(); return; } if (!err.isCanceled()) { information(i18nc("@info", "The key was revoked successfully."), i18nc("@title:window", "Key Revoked")); } finished(); } void RevokeKeyCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred during the revocation:" "%1", Formatting::errorAsString(err)), i18nc("@title:window", "Revocation Failed")); } RevokeKeyCommand::RevokeKeyCommand(QAbstractItemView *v, KeyListController *c) : Command{v, new Private{this, c}} { } RevokeKeyCommand::RevokeKeyCommand(const GpgME::Key &key) : Command{key, new Private{this}} { } RevokeKeyCommand::~RevokeKeyCommand() = default; void RevokeKeyCommand::doStart() { d->start(); } void RevokeKeyCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_revokekeycommand.cpp" diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp index 1caf8a3d9..f0eced85d 100644 --- a/src/dialogs/revokekeydialog.cpp +++ b/src/dialogs/revokekeydialog.cpp @@ -1,311 +1,136 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokekeydialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "revokekeydialog.h" #include "utils/accessibility.h" #include "view/errorlabel.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 GpgME; -namespace -{ -class TextEdit : public QTextEdit -{ - Q_OBJECT -public: - using QTextEdit::QTextEdit; - -Q_SIGNALS: - void editingFinished(); - -protected: - void focusOutEvent(QFocusEvent *event) override - { - Qt::FocusReason reason = event->reason(); - if (reason != Qt::PopupFocusReason || !(QApplication::activePopupWidget() && QApplication::activePopupWidget()->parentWidget() == this)) { - Q_EMIT editingFinished(); - } - - QTextEdit::focusOutEvent(event); - } - QSize minimumSizeHint() const override - { - return {0, fontMetrics().height() * 3}; - } -}; -} - class RevokeKeyDialog::Private { friend class ::Kleo::RevokeKeyDialog; RevokeKeyDialog *const q; struct { QLabel *infoLabel = nullptr; - QLabel *descriptionLabel = nullptr; - TextEdit *description = nullptr; - ErrorLabel *descriptionError = nullptr; QDialogButtonBox *buttonBox = nullptr; } ui; Key key; - QButtonGroup reasonGroup; - bool descriptionEditingInProgress = false; - QString descriptionAccessibleName; public: Private(RevokeKeyDialog *qq) : q(qq) { q->setWindowTitle(i18nc("title:window", "Revoke Certificate")); auto mainLayout = new QVBoxLayout{q}; ui.infoLabel = new QLabel{q}; - auto infoGroupBox = new QGroupBox{i18nc("@title:group", "Information")}; - infoGroupBox->setFlat(true); - auto infoLayout = new QVBoxLayout; - infoGroupBox->setLayout(infoLayout); - infoLayout->addWidget(ui.infoLabel); - mainLayout->addWidget(infoGroupBox); - - auto groupBox = new QGroupBox{i18nc("@title:group", "Reason for revocation"), q}; - groupBox->setFlat(true); - - reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "No reason specified"), q}, static_cast(RevocationReason::Unspecified)); - reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Certificate has been compromised"), q}, static_cast(RevocationReason::Compromised)); - reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Certificate is superseded"), q}, static_cast(RevocationReason::Superseded)); - reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Certificate is no longer used"), q}, static_cast(RevocationReason::NoLongerUsed)); - reasonGroup.button(static_cast(RevocationReason::Unspecified))->setChecked(true); - - { - auto boxLayout = new QVBoxLayout{groupBox}; - for (auto radio : reasonGroup.buttons()) { - boxLayout->addWidget(radio); - } - } - - mainLayout->addWidget(groupBox); - - { - ui.descriptionLabel = new QLabel{i18nc("@label:textbox", "Description (optional):"), q}; - ui.description = new TextEdit{q}; - ui.description->setAcceptRichText(false); - // do not accept Tab as input; this is better for accessibility and - // tabulators are not really that useful in the description - ui.description->setTabChangesFocus(true); - ui.descriptionLabel->setBuddy(ui.description); - ui.descriptionError = new ErrorLabel{q}; - ui.descriptionError->setVisible(false); - - mainLayout->addWidget(ui.descriptionLabel); - mainLayout->addWidget(ui.description); - mainLayout->addWidget(ui.descriptionError); - } - - connect(ui.description, &TextEdit::editingFinished, q, [this]() { - onDescriptionEditingFinished(); - }); - connect(ui.description, &TextEdit::textChanged, q, [this]() { - onDescriptionTextChanged(); - }); + mainLayout->addWidget(ui.infoLabel); ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = ui.buttonBox->button(QDialogButtonBox::Ok); okButton->setText(i18nc("@action:button", "Revoke Certificate")); okButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete-remove"))); mainLayout->addWidget(ui.buttonBox); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); }); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); restoreGeometry(); } ~Private() { saveGeometry(); } private: void saveGeometry() { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), QStringLiteral("RevokeKeyDialog")); cfgGroup.writeEntry("Size", q->size()); cfgGroup.sync(); } void restoreGeometry(const QSize &defaultSize = {}) { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), QStringLiteral("RevokeKeyDialog")); const QSize size = cfgGroup.readEntry("Size", defaultSize); if (size.isValid()) { q->resize(size); } else { q->resize(q->minimumSizeHint()); } } void checkAccept() { - if (!descriptionHasAcceptableInput()) { - KMessageBox::error(q, descriptionErrorMessage()); - } else { - q->accept(); - } - } - - bool descriptionHasAcceptableInput() const - { - return !q->description().contains(QLatin1StringView{"\n\n"}); - } - - QString descriptionErrorMessage() const - { - QString message; - - if (!descriptionHasAcceptableInput()) { - message = i18n("Error: The description must not contain empty lines."); - } - return message; - } - - void updateDescriptionError() - { - const auto currentErrorMessage = ui.descriptionError->text(); - const auto newErrorMessage = descriptionErrorMessage(); - if (newErrorMessage == currentErrorMessage) { - return; - } - if (currentErrorMessage.isEmpty() && descriptionEditingInProgress) { - // delay showing the error message until editing is finished, so that we - // do not annoy the user with an error message while they are still - // entering the recipient; - // on the other hand, we clear the error message immediately if it does - // not apply anymore and we update the error message immediately if it - // changed - return; - } - ui.descriptionError->setVisible(!newErrorMessage.isEmpty()); - ui.descriptionError->setText(newErrorMessage); - updateAccessibleNameAndDescription(); - } - - void updateAccessibleNameAndDescription() - { - // fall back to default accessible name if accessible name wasn't set explicitly - if (descriptionAccessibleName.isEmpty()) { - descriptionAccessibleName = getAccessibleName(ui.description); - } - const bool errorShown = ui.descriptionError->isVisible(); - - // Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute); - // emulate this by setting the error message as accessible description of the input field - const auto description = errorShown ? ui.descriptionError->text() : QString{}; - if (ui.description->accessibleDescription() != description) { - ui.description->setAccessibleDescription(description); - } - - // Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute); - // screen readers say something like "invalid entry" if this state is set; - // emulate this by adding "invalid entry" to the accessible name of the input field - // and its label - const auto name = errorShown ? descriptionAccessibleName + QLatin1StringView{", "} + invalidEntryText() // - : descriptionAccessibleName; - if (ui.descriptionLabel->accessibleName() != name) { - ui.descriptionLabel->setAccessibleName(name); - } - if (ui.description->accessibleName() != name) { - ui.description->setAccessibleName(name); - } - } - - void onDescriptionTextChanged() - { - descriptionEditingInProgress = true; - updateDescriptionError(); - } - - void onDescriptionEditingFinished() - { - descriptionEditingInProgress = false; - updateDescriptionError(); + q->accept(); } }; RevokeKeyDialog::RevokeKeyDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d{new Private{this}} { } RevokeKeyDialog::~RevokeKeyDialog() = default; void RevokeKeyDialog::setKey(const GpgME::Key &key) { d->key = key; d->ui.infoLabel->setText( xi18nc("@info", "You are about to revoke the following certificate:        %1The " "revocation will take effect " "immediately and " "cannot be reverted.Consequences: It will no longer be possible to sign using this " "certificate.It will still be possible to " "decrypt using this certificate.Other people will no longer be able to encrypt for this certificate after receiving the " "revocation.") .arg(Formatting::summaryLine(key))); } -GpgME::RevocationReason RevokeKeyDialog::reason() const -{ - return static_cast(d->reasonGroup.checkedId()); -} - -QString RevokeKeyDialog::description() const -{ - static const QRegularExpression whitespaceAtEndOfLine{QStringLiteral(R"([ \t\r]+\n)")}; - static const QRegularExpression trailingWhitespace{QStringLiteral(R"(\s*$)")}; - return d->ui.description->toPlainText().remove(whitespaceAtEndOfLine).remove(trailingWhitespace); -} - -#include "revokekeydialog.moc" - #include "moc_revokekeydialog.cpp" diff --git a/src/dialogs/revokekeydialog.h b/src/dialogs/revokekeydialog.h index 876198e09..1d8847e25 100644 --- a/src/dialogs/revokekeydialog.h +++ b/src/dialogs/revokekeydialog.h @@ -1,45 +1,41 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokekeydialog.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include namespace GpgME { class Key; -enum class RevocationReason; } namespace Kleo { class RevokeKeyDialog : public QDialog { Q_OBJECT public: explicit RevokeKeyDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~RevokeKeyDialog() override; void setKey(const GpgME::Key &key); - GpgME::RevocationReason reason() const; - QString description() const; - private: class Private; const std::unique_ptr d; }; } // namespace Kleo