Page MenuHome GnuPG

No OneTemporary

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3b0be5f68..88c564965 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,390 +1,391 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(icons)
add_subdirectory(mimetypes)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
if (NOT DISABLE_KWATCHGNUPG)
add_subdirectory(kwatchgnupg)
endif()
add_subdirectory(libkleopatraclient)
add_subdirectory(conf)
add_subdirectory(kconf_update)
if(WIN32)
set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp)
set(_kleopatra_extra_SRCS
utils/gnupg-registry.c
selftest/registrycheck.cpp
utils/windowsprocessdevice.cpp
utils/userinfo_win.cpp
)
else()
set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp)
set(_kleopatra_extra_SRCS)
endif()
set(_kleopatra_uiserver_SRCS
uiserver/sessiondata.cpp
uiserver/uiserver.cpp
${_kleopatra_extra_uiserver_SRCS}
uiserver/assuanserverconnection.cpp
uiserver/echocommand.cpp
uiserver/decryptverifycommandemailbase.cpp
uiserver/decryptverifycommandfilesbase.cpp
uiserver/signcommand.cpp
uiserver/signencryptfilescommand.cpp
uiserver/prepencryptcommand.cpp
uiserver/prepsigncommand.cpp
uiserver/encryptcommand.cpp
uiserver/selectcertificatecommand.cpp
uiserver/importfilescommand.cpp
uiserver/createchecksumscommand.cpp
uiserver/verifychecksumscommand.cpp
selftest/uiservercheck.cpp
)
if(ASSUAN2_FOUND)
include_directories(${ASSUAN2_INCLUDES})
set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES})
else()
include_directories(${ASSUAN_INCLUDES})
if(WIN32)
set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES})
else()
set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES})
endif()
endif()
if(HAVE_GPG_ERR_SOURCE_KLEO)
add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO)
add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO)
else()
add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1)
add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1)
endif()
ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui)
set(_kleopatra_SRCS
+ utils/accessibility.cpp
utils/gui-helper.cpp
utils/filedialog.cpp
utils/kdpipeiodevice.cpp
utils/headerview.cpp
utils/scrollarea.cpp
utils/dragqueen.cpp
utils/multivalidator.cpp
utils/systemtrayicon.cpp
utils/path-helper.cpp
utils/input.cpp
utils/output.cpp
utils/validation.cpp
utils/wsastarter.cpp
utils/iodevicelogger.cpp
utils/log.cpp
utils/action_data.cpp
utils/types.cpp
utils/archivedefinition.cpp
utils/auditlog.cpp
utils/clipboardmenu.cpp
utils/kuniqueservice.cpp
utils/tags.cpp
utils/writecertassuantransaction.cpp
utils/keyparameters.cpp
utils/userinfo.cpp
selftest/selftest.cpp
selftest/enginecheck.cpp
selftest/gpgconfcheck.cpp
selftest/gpgagentcheck.cpp
selftest/libkleopatrarccheck.cpp
selftest/compliancecheck.cpp
${_kleopatra_extra_SRCS}
view/errorlabel.cpp
view/htmllabel.cpp
view/keylistcontroller.cpp
view/keytreeview.cpp
view/searchbar.cpp
view/smartcardwidget.cpp
view/openpgpkeycardwidget.cpp
view/padwidget.cpp
view/pgpcardwidget.cpp
view/pivcardwidget.cpp
view/p15cardwidget.cpp
view/netkeywidget.cpp
view/nullpinwidget.cpp
view/tabwidget.cpp
view/keycacheoverlay.cpp
view/urllabel.cpp
view/waitwidget.cpp
view/welcomewidget.cpp
dialogs/certificateselectiondialog.cpp
dialogs/certifywidget.cpp
dialogs/expirydialog.cpp
dialogs/lookupcertificatesdialog.cpp
dialogs/ownertrustdialog.cpp
dialogs/selftestdialog.cpp
dialogs/certifycertificatedialog.cpp
dialogs/revokecertificationwidget.cpp
dialogs/revokecertificationdialog.cpp
dialogs/adduseriddialog.cpp
dialogs/deletecertificatesdialog.cpp
dialogs/setinitialpindialog.cpp
dialogs/certificatedetailsdialog.cpp
dialogs/certificatedetailswidget.cpp
dialogs/trustchainwidget.cpp
dialogs/weboftrustwidget.cpp
dialogs/weboftrustdialog.cpp
dialogs/exportdialog.cpp
dialogs/subkeyswidget.cpp
dialogs/gencardkeydialog.cpp
dialogs/updatenotification.cpp
dialogs/pivcardapplicationadministrationkeyinputdialog.cpp
dialogs/certificatedetailsinputwidget.cpp
dialogs/createcsrforcardkeydialog.cpp
dialogs/groupdetailsdialog.cpp
dialogs/editgroupdialog.cpp
dialogs/revokekeydialog.cpp
crypto/controller.cpp
crypto/certificateresolver.cpp
crypto/sender.cpp
crypto/recipient.cpp
crypto/task.cpp
crypto/taskcollection.cpp
crypto/decryptverifytask.cpp
crypto/decryptverifyemailcontroller.cpp
crypto/decryptverifyfilescontroller.cpp
crypto/autodecryptverifyfilescontroller.cpp
crypto/encryptemailtask.cpp
crypto/encryptemailcontroller.cpp
crypto/newsignencryptemailcontroller.cpp
crypto/signencrypttask.cpp
crypto/signencryptfilescontroller.cpp
crypto/signemailtask.cpp
crypto/signemailcontroller.cpp
crypto/createchecksumscontroller.cpp
crypto/verifychecksumscontroller.cpp
crypto/gui/wizard.cpp
crypto/gui/wizardpage.cpp
crypto/gui/certificateselectionline.cpp
crypto/gui/certificatelineedit.cpp
crypto/gui/signingcertificateselectionwidget.cpp
crypto/gui/signingcertificateselectiondialog.cpp
crypto/gui/resultitemwidget.cpp
crypto/gui/resultlistwidget.cpp
crypto/gui/resultpage.cpp
crypto/gui/newresultpage.cpp
crypto/gui/signencryptfileswizard.cpp
crypto/gui/signencryptemailconflictdialog.cpp
crypto/gui/decryptverifyoperationwidget.cpp
crypto/gui/decryptverifyfileswizard.cpp
crypto/gui/decryptverifyfilesdialog.cpp
crypto/gui/objectspage.cpp
crypto/gui/resolverecipientspage.cpp
crypto/gui/signerresolvepage.cpp
crypto/gui/encryptemailwizard.cpp
crypto/gui/signemailwizard.cpp
crypto/gui/signencryptwidget.cpp
crypto/gui/signencryptwizard.cpp
crypto/gui/unknownrecipientwidget.cpp
crypto/gui/verifychecksumsdialog.cpp
commands/command.cpp
commands/gnupgprocesscommand.cpp
commands/detailscommand.cpp
commands/exportcertificatecommand.cpp
commands/exportgroupscommand.cpp
commands/importcertificatescommand.cpp
commands/importcertificatefromfilecommand.cpp
commands/importcertificatefromclipboardcommand.cpp
commands/importcertificatefromdatacommand.cpp
commands/importcertificatefromkeyservercommand.cpp
commands/lookupcertificatescommand.cpp
commands/reloadkeyscommand.cpp
commands/refreshx509certscommand.cpp
commands/refreshopenpgpcertscommand.cpp
commands/deletecertificatescommand.cpp
commands/decryptverifyfilescommand.cpp
commands/signencryptfilescommand.cpp
commands/signencryptfoldercommand.cpp
commands/encryptclipboardcommand.cpp
commands/signclipboardcommand.cpp
commands/decryptverifyclipboardcommand.cpp
commands/clearcrlcachecommand.cpp
commands/dumpcrlcachecommand.cpp
commands/dumpcertificatecommand.cpp
commands/importcrlcommand.cpp
commands/changeexpirycommand.cpp
commands/changeownertrustcommand.cpp
commands/changeroottrustcommand.cpp
commands/changepassphrasecommand.cpp
commands/certifycertificatecommand.cpp
commands/revokecertificationcommand.cpp
commands/selftestcommand.cpp
commands/exportsecretkeycommand.cpp
commands/exportsecretkeycommand_old.cpp
commands/exportsecretsubkeycommand.cpp
commands/exportopenpgpcertstoservercommand.cpp
commands/adduseridcommand.cpp
commands/newcertificatecommand.cpp
commands/setinitialpincommand.cpp
commands/learncardkeyscommand.cpp
commands/checksumcreatefilescommand.cpp
commands/checksumverifyfilescommand.cpp
commands/exportpaperkeycommand.cpp
commands/importpaperkeycommand.cpp
commands/genrevokecommand.cpp
commands/keytocardcommand.cpp
commands/cardcommand.cpp
commands/pivgeneratecardkeycommand.cpp
commands/changepincommand.cpp
commands/authenticatepivcardapplicationcommand.cpp
commands/setpivcardapplicationadministrationkeycommand.cpp
commands/certificatetopivcardcommand.cpp
commands/importcertificatefrompivcardcommand.cpp
commands/createopenpgpkeyfromcardkeyscommand.cpp
commands/createcsrforcardkeycommand.cpp
commands/revokekeycommand.cpp
${_kleopatra_uiserver_files}
conf/configuredialog.cpp
conf/groupsconfigdialog.cpp
conf/groupsconfigpage.cpp
conf/groupsconfigwidget.cpp
newcertificatewizard/listwidget.cpp
newcertificatewizard/newcertificatewizard.cpp
smartcard/readerstatus.cpp
smartcard/card.cpp
smartcard/openpgpcard.cpp
smartcard/netkeycard.cpp
smartcard/pivcard.cpp
smartcard/p15card.cpp
smartcard/keypairinfo.cpp
smartcard/utils.cpp
smartcard/deviceinfowatcher.cpp
accessibility/accessiblerichtextlabel.cpp
accessibility/accessiblewidgetfactory.cpp
aboutdata.cpp
systrayicon.cpp
kleopatraapplication.cpp
mainwindow.cpp
main.cpp
kleopatra.qrc
)
if(WIN32)
configure_file (versioninfo.rc.in versioninfo.rc)
set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS})
endif()
set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS})
ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra
DESCRIPTION "kleopatra (kleopatra)"
OLD_CATEGORY_NAMES log_kleopatra
EXPORT KLEOPATRA
)
if(KLEO_MODEL_TEST)
add_definitions(-DKLEO_MODEL_TEST)
set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp)
endif()
ki18n_wrap_ui(_kleopatra_SRCS
dialogs/ownertrustdialog.ui
dialogs/selectchecklevelwidget.ui
dialogs/selftestdialog.ui
dialogs/setinitialpindialog.ui
dialogs/trustchainwidget.ui
dialogs/subkeyswidget.ui
newcertificatewizard/listwidget.ui
newcertificatewizard/chooseprotocolpage.ui
newcertificatewizard/enterdetailspage.ui
newcertificatewizard/keycreationpage.ui
newcertificatewizard/resultpage.ui
newcertificatewizard/advancedsettingsdialog.ui
)
kconfig_add_kcfg_files(_kleopatra_SRCS
kcfg/tooltippreferences.kcfgc
kcfg/emailoperationspreferences.kcfgc
kcfg/fileoperationspreferences.kcfgc
kcfg/smimevalidationpreferences.kcfgc
kcfg/tagspreferences.kcfgc
kcfg/settings.kcfgc
)
file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png")
ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS})
add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS})
# For the ConfigureDialog & KCMs
target_link_libraries(kleopatra_bin kcm_kleopatra_static)
#if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
# set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON)
#endif()
set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra)
if (WIN32)
set(_kleopatra_platform_libs "secur32")
endif ()
target_link_libraries(kleopatra_bin
Gpgmepp
QGpgme
${_kleopatra_extra_libs}
KF5::Libkleo
KF5::Mime
KF5::I18n
KF5::XmlGui
KF5::IconThemes
KF5::WindowSystem
KF5::CoreAddons
KF5::ItemModels
KF5::Crash
Qt${QT_MAJOR_VERSION}::Network
Qt${QT_MAJOR_VERSION}::PrintSupport # Printing secret keys
${_kleopatra_uiserver_extra_libs}
${_kleopatra_dbusaddons_libs}
kleopatraclientcore
${_kleopatra_platform_libs}
)
install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(
PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop
DESTINATION ${KDE_INSTALL_APPDIR}
)
install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(
PROGRAMS data/kleopatra_signencryptfiles.desktop
data/kleopatra_signencryptfolders.desktop
data/kleopatra_decryptverifyfiles.desktop
data/kleopatra_decryptverifyfolders.desktop
DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus
)
diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp
index 0882a42f0..38f972cd0 100644
--- a/src/crypto/gui/certificatelineedit.cpp
+++ b/src/crypto/gui/certificatelineedit.cpp
@@ -1,617 +1,599 @@
/* crypto/gui/certificatelineedit.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "certificatelineedit.h"
#include "commands/detailscommand.h"
#include "dialogs/groupdetailsdialog.h"
+#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include <QAccessible>
#include <QCompleter>
#include <QPushButton>
#include <QAction>
#include <QSignalBlocker>
#include "kleopatra_debug.h"
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <gpgme++/key.h>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QToolButton>
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(KeyGroup)
static QStringList s_lookedUpKeys;
namespace
{
class CompletionProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
CompletionProxyModel(QObject *parent = nullptr)
: KeyListSortFilterProxyModel(parent)
{
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// completion pop-up
return 1;
}
QVariant data(const QModelIndex &idx, int role) const override
{
if (!idx.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DecorationRole: {
const auto key = KeyListSortFilterProxyModel::data(idx, KeyList::KeyRole).value<GpgME::Key>();
if (!key.isNull()) {
return Kleo::Formatting::iconForUid(key.userID(0));
}
const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value<KeyGroup>();
if (!group.isNull()) {
return QIcon::fromTheme(QStringLiteral("group"));
}
Q_ASSERT(!key.isNull() || !group.isNull());
return QVariant();
}
default:
return KeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role);
}
}
};
auto createSeparatorAction(QObject *parent)
{
auto action = new QAction{parent};
action->setSeparator(true);
return action;
}
} // namespace
class CertificateLineEdit::Private
{
CertificateLineEdit *q;
public:
enum class Status
{
Empty, //< text is empty
Success, //< a certificate or group is set
None, //< entered text does not match any certificates or groups
Ambiguous, //< entered text matches multiple certificates or groups
};
explicit Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter);
QString text() const;
void setKey(const GpgME::Key &key);
void setGroup(const KeyGroup &group);
void setKeyFilter(const std::shared_ptr<KeyFilter> &filter);
void setAccessibleName(const QString &s);
private:
void updateKey();
void editChanged();
void editFinished();
void checkLocate();
void openDetailsDialog();
void setTextWithBlockedSignals(const QString &s);
void showContextMenu(const QPoint &pos);
QString errorMessage() const;
void updateErrorLabel();
void updateAccessibleNameAndDescription();
public:
Status mStatus = Status::Empty;
GpgME::Key mKey;
KeyGroup mGroup;
struct Ui {
explicit Ui(QWidget *parent)
: lineEdit{parent}
, button{parent}
, errorLabel{parent}
{}
QLineEdit lineEdit;
QToolButton button;
ErrorLabel errorLabel;
} ui;
private:
QString mAccessibleName;
KeyListSortFilterProxyModel *const mFilterModel;
KeyListSortFilterProxyModel *const mCompleterFilterModel;
QCompleter *mCompleter = nullptr;
std::shared_ptr<KeyFilter> mFilter;
bool mEditingInProgress = false;
QAction *const mStatusAction;
QAction *const mShowDetailsAction;
};
CertificateLineEdit::Private::Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter)
: q{qq}
, ui{qq}
, mFilterModel{new KeyListSortFilterProxyModel{qq}}
, mCompleterFilterModel{new CompletionProxyModel{qq}}
, mCompleter{new QCompleter{qq}}
, mFilter{std::shared_ptr<KeyFilter>{filter}}
, mStatusAction{new QAction{qq}}
, mShowDetailsAction{new QAction{qq}}
{
ui.lineEdit.setPlaceholderText(i18n("Please enter a name or email address..."));
ui.lineEdit.setClearButtonEnabled(true);
ui.lineEdit.setContextMenuPolicy(Qt::CustomContextMenu);
ui.lineEdit.addAction(mStatusAction, QLineEdit::LeadingPosition);
mCompleterFilterModel->setKeyFilter(mFilter);
mCompleterFilterModel->setSourceModel(model);
mCompleter->setModel(mCompleterFilterModel);
mCompleter->setFilterMode(Qt::MatchContains);
mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
ui.lineEdit.setCompleter(mCompleter);
ui.button.setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
ui.button.setToolTip(i18n("Show certificate list"));
ui.button.setAccessibleName(i18n("Show certificate list"));
ui.errorLabel.setVisible(false);
auto vbox = new QVBoxLayout{q};
vbox->setContentsMargins(0, 0, 0, 0);
auto l = new QHBoxLayout;
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(&ui.lineEdit);
l->addWidget(&ui.button);
vbox->addLayout(l);
vbox->addWidget(&ui.errorLabel);
q->setFocusPolicy(ui.lineEdit.focusPolicy());
q->setFocusProxy(&ui.lineEdit);
mShowDetailsAction->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
mShowDetailsAction->setText(i18nc("@action:inmenu", "Show Details"));
mShowDetailsAction->setEnabled(false);
mFilterModel->setSourceModel(model);
mFilterModel->setFilterKeyColumn(KeyList::Summary);
if (filter) {
mFilterModel->setKeyFilter(mFilter);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keyListingDone,
q, [this]() { updateKey(); });
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated,
q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
setTextWithBlockedSignals(Formatting::summaryLine(group));
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(q, [this]() { updateKey(); }, Qt::QueuedConnection);
}
});
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved,
q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
mGroup = KeyGroup();
QSignalBlocker blocky{&ui.lineEdit};
ui.lineEdit.clear();
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(q, [this]() { updateKey(); }, Qt::QueuedConnection);
}
});
connect(&ui.lineEdit, &QLineEdit::editingFinished,
q, [this]() {
// queue the call of editFinished() to ensure that QCompleter::activated is handled first
QMetaObject::invokeMethod(q, [this]() { editFinished(); }, Qt::QueuedConnection);
});
connect(&ui.lineEdit, &QLineEdit::textChanged,
q, [this]() { editChanged(); });
connect(&ui.lineEdit, &QLineEdit::customContextMenuRequested,
q, [this](const QPoint &pos) { showContextMenu(pos); });
connect(mStatusAction, &QAction::triggered,
q, [this]() { openDetailsDialog(); });
connect(mShowDetailsAction, &QAction::triggered,
q, [this]() { openDetailsDialog(); });
connect(&ui.button, &QToolButton::clicked,
q, &CertificateLineEdit::certificateSelectionRequested);
connect(mCompleter, qOverload<const QModelIndex &>(&QCompleter::activated),
q, [this] (const QModelIndex &index) {
Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value<Key>();
auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value<KeyGroup>();
if (!key.isNull()) {
q->setKey(key);
} else if (!group.isNull()) {
q->setGroup(group);
} else {
qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group";
}
});
updateKey();
}
void CertificateLineEdit::Private::openDetailsDialog()
{
if (!q->key().isNull()) {
auto cmd = new Commands::DetailsCommand{q->key(), nullptr};
cmd->setParentWidget(q);
cmd->start();
} else if (!q->group().isNull()) {
auto dlg = new Dialogs::GroupDetailsDialog{q};
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setGroup(q->group());
dlg->show();
}
}
void CertificateLineEdit::Private::setTextWithBlockedSignals(const QString &s)
{
QSignalBlocker blocky{&ui.lineEdit};
ui.lineEdit.setText(s);
}
void CertificateLineEdit::Private::showContextMenu(const QPoint &pos)
{
if (QMenu *menu = ui.lineEdit.createStandardContextMenu()) {
auto *const firstStandardAction = menu->actions().value(0);
menu->insertActions(firstStandardAction,
{mShowDetailsAction, createSeparatorAction(menu)});
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(ui.lineEdit.mapToGlobal(pos));
}
}
CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model,
KeyFilter *filter,
QWidget *parent)
: QWidget{parent}
, d{new Private{this, model, filter}}
{
/* Take ownership of the model to prevent double deletion when the
* filter models are deleted */
model->setParent(parent ? parent : this);
}
CertificateLineEdit::~CertificateLineEdit() = default;
void CertificateLineEdit::Private::editChanged()
{
const bool editingStarted = !mEditingInProgress;
mEditingInProgress = true;
updateKey();
if (editingStarted) {
Q_EMIT q->editingStarted();
}
}
void CertificateLineEdit::Private::editFinished()
{
mEditingInProgress = false;
updateKey();
if (!q->key().isNull()) {
setTextWithBlockedSignals(Formatting::summaryLine(q->key()));
} else if (!q->group().isNull()) {
setTextWithBlockedSignals(Formatting::summaryLine(q->group()));
} else if (mStatus == Status::None) {
checkLocate();
}
}
void CertificateLineEdit::Private::checkLocate()
{
if (mStatus != Status::None) {
// try to locate key only if text matches no local certificates or groups
return;
}
// Only check once per mailbox
const auto mailText = ui.lineEdit.text().trimmed();
if (mailText.isEmpty() || s_lookedUpKeys.contains(mailText)) {
return;
}
s_lookedUpKeys << mailText;
qCDebug(KLEOPATRA_LOG) << "Lookup job for" << mailText;
auto job = QGpgME::openpgp()->locateKeysJob();
job->start({mailText}, /*secretOnly=*/false);
}
void CertificateLineEdit::Private::updateKey()
{
static const _detail::ByFingerprint<std::equal_to> keysHaveSameFingerprint;
const auto mailText = ui.lineEdit.text().trimmed();
auto newKey = Key();
auto newGroup = KeyGroup();
if (mailText.isEmpty()) {
mStatus = Status::Empty;
mStatusAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-unavailable")));
mStatusAction->setToolTip({});
} else {
mFilterModel->setFilterFixedString(mailText);
if (mFilterModel->rowCount() > 1) {
// keep current key or group if they still match
if (!mKey.isNull()) {
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
Key key = mFilterModel->key(index);
if (!key.isNull() && keysHaveSameFingerprint(key, mKey)) {
newKey = mKey;
break;
}
}
} else if (!mGroup.isNull()) {
newGroup = mGroup;
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
KeyGroup group = mFilterModel->group(index);
if (!group.isNull() && group.source() == mGroup.source() && group.id() == mGroup.id()) {
newGroup = mGroup;
break;
}
}
}
if (newKey.isNull() && newGroup.isNull()) {
mStatus = Status::Ambiguous;
mStatusAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-question")));
mStatusAction->setToolTip(i18n("Multiple matching certificates or groups found"));
}
} else if (mFilterModel->rowCount() == 1) {
const auto index = mFilterModel->index(0, 0);
newKey = mFilterModel->data(index, KeyList::KeyRole).value<Key>();
newGroup = mFilterModel->data(index, KeyList::GroupRole).value<KeyGroup>();
Q_ASSERT(!newKey.isNull() || !newGroup.isNull());
if (newKey.isNull() && newGroup.isNull()) {
mStatus = Status::None;
mStatusAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")));
mStatusAction->setToolTip(i18n("No matching certificates or groups found"));
}
} else {
mStatus = Status::None;
mStatusAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")));
mStatusAction->setToolTip(i18n("No matching certificates or groups found"));
}
}
mKey = newKey;
mGroup = newGroup;
if (!mKey.isNull()) {
/* FIXME: This needs to be solved by a multiple UID supporting model */
mStatus = Status::Success;
mStatusAction->setIcon(Formatting::iconForUid(mKey.userID(0)));
mStatusAction->setToolTip(Formatting::validity(mKey.userID(0)));
ui.lineEdit.setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions));
} else if (!mGroup.isNull()) {
mStatus = Status::Success;
mStatusAction->setIcon(Formatting::validityIcon(mGroup));
mStatusAction->setToolTip(Formatting::validity(mGroup));
ui.lineEdit.setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions));
} else {
ui.lineEdit.setToolTip({});
}
mShowDetailsAction->setEnabled(mStatus == Status::Success);
updateErrorLabel();
Q_EMIT q->keyChanged();
}
QString CertificateLineEdit::Private::errorMessage() const
{
switch (mStatus) {
case Status::Empty:
case Status::Success:
return {};
case Status::None:
return i18n("Error: No matching certificates or groups found");
case Status::Ambiguous:
return i18n("Error: Multiple matching certificates or groups found");
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
void CertificateLineEdit::Private::updateErrorLabel()
{
const auto currentErrorMessage = ui.errorLabel.text();
const auto newErrorMessage = errorMessage();
if (newErrorMessage == currentErrorMessage) {
return;
}
if (currentErrorMessage.isEmpty() && mEditingInProgress) {
// 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.errorLabel.setVisible(!newErrorMessage.isEmpty());
ui.errorLabel.setText(newErrorMessage);
updateAccessibleNameAndDescription();
}
void CertificateLineEdit::Private::setAccessibleName(const QString &s)
{
mAccessibleName = s;
updateAccessibleNameAndDescription();
}
-namespace
-{
-QString getAccessibleName(QObject *object)
-{
- QString name;
- if (const auto *const iface = QAccessible::queryAccessibleInterface(object)) {
- name = iface->text(QAccessible::Name);
- }
- return name;
-}
-
-QString invalidEntryText()
-{
- return i18nc("text for screen readers to indicate that the associated object, "
- "such as a form field, has an error",
- "invalid entry");
-}
-}
-
void CertificateLineEdit::Private::updateAccessibleNameAndDescription()
{
// fall back to default accessible name if accessible name wasn't set explicitly
if (mAccessibleName.isEmpty()) {
mAccessibleName = getAccessibleName(&ui.lineEdit);
}
const bool errorShown = ui.errorLabel.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.errorLabel.text() : QString{};
if (ui.lineEdit.accessibleDescription() != description) {
ui.lineEdit.setAccessibleDescription(description);
}
// Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute);
// screen readers say something like "invalid data" if this state is set;
// emulate this by adding "invalid data" to the accessible name of the input field
const auto name = errorShown ? mAccessibleName + QLatin1String{", "} + invalidEntryText()
: mAccessibleName;
if (ui.lineEdit.accessibleName() != name) {
ui.lineEdit.setAccessibleName(name);
}
}
Key CertificateLineEdit::key() const
{
if (isEnabled()) {
return d->mKey;
} else {
return Key();
}
}
KeyGroup CertificateLineEdit::group() const
{
if (isEnabled()) {
return d->mGroup;
} else {
return KeyGroup();
}
}
QString CertificateLineEdit::Private::text() const
{
return ui.lineEdit.text().trimmed();
}
QString CertificateLineEdit::text() const
{
return d->text();
}
void CertificateLineEdit::Private::setKey(const Key &key)
{
mKey = key;
mGroup = KeyGroup();
qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key);
setTextWithBlockedSignals(Formatting::summaryLine(key));
updateKey();
}
void CertificateLineEdit::setKey(const Key &key)
{
d->setKey(key);
}
void CertificateLineEdit::Private::setGroup(const KeyGroup &group)
{
mGroup = group;
mKey = Key();
const QString summary = Formatting::summaryLine(group);
qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary;
setTextWithBlockedSignals(summary);
updateKey();
}
void CertificateLineEdit::setGroup(const KeyGroup &group)
{
d->setGroup(group);
}
bool CertificateLineEdit::isEmpty() const
{
return d->mStatus == Private::Status::Empty;
}
bool CertificateLineEdit::hasAcceptableInput() const
{
return d->mStatus == Private::Status::Empty
|| d->mStatus == Private::Status::Success;
}
void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
mFilter = filter;
mFilterModel->setKeyFilter(filter);
mCompleterFilterModel->setKeyFilter(mFilter);
updateKey();
}
void CertificateLineEdit::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
d->setKeyFilter(filter);
}
void CertificateLineEdit::setAccessibleNameOfLineEdit(const QString &name)
{
d->setAccessibleName(name);
}
#include "certificatelineedit.moc"
diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp
index 184a6cd6e..5435cf3f7 100644
--- a/src/dialogs/revokekeydialog.cpp
+++ b/src/dialogs/revokekeydialog.cpp
@@ -1,315 +1,300 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "revokekeydialog.h"
+#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include <Libkleo/Formatting>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QAccessible>
#include <QApplication>
#include <QButtonGroup>
#include <QDialogButtonBox>
#include <QFocusEvent>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QRegularExpression>
#include <QTextEdit>
#include <QVBoxLayout>
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
#include <gpgme++/global.h>
#endif
#include <gpgme++/key.h>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace GpgME;
namespace
{
-QString getAccessibleName(QObject *object)
-{
- QString name;
- if (const auto *const iface = QAccessible::queryAccessibleInterface(object)) {
- name = iface->text(QAccessible::Name);
- }
- return name;
-}
-
-QString invalidEntryText()
-{
- return i18nc("text for screen readers to indicate that the associated object, "
- "such as a form field, has an error",
- "invalid entry");
-}
-
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);
}
};
}
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 Key"));
auto mainLayout = new QVBoxLayout{q};
ui.infoLabel = new QLabel{q};
mainLayout->addWidget(ui.infoLabel);
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
auto groupBox = new QGroupBox{i18nc("@title:group", "Reason for revocation"), q};
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "No reason specified"), q},
static_cast<int>(RevocationReason::Unspecified));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key has been compromised"), q},
static_cast<int>(RevocationReason::Compromised));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is superseded"), q},
static_cast<int>(RevocationReason::Superseded));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is no longer used"), q},
static_cast<int>(RevocationReason::NoLongerUsed));
reasonGroup.button(static_cast<int>(RevocationReason::Unspecified))->setChecked(true);
{
auto boxLayout = new QVBoxLayout{groupBox};
for (auto radio : reasonGroup.buttons()) {
boxLayout->addWidget(radio);
}
}
mainLayout->addWidget(groupBox);
#endif
{
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(); });
ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto okButton = ui.buttonBox->button(QDialogButtonBox::Ok);
okButton->setText(i18nc("@action:button", "Revoke Key"));
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(), "RevokeKeyDialog");
cfgGroup.writeEntry("Size", q->size());
cfgGroup.sync();
}
void restoreGeometry(const QSize &defaultSize = {})
{
KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "RevokeKeyDialog");
const QSize size = cfgGroup.readEntry("Size", defaultSize);
if (size.isValid()) {
q->resize(size);
}
}
void checkAccept()
{
if (!descriptionHasAcceptableInput()) {
KMessageBox::sorry(q, descriptionErrorMessage());
} else {
q->accept();
}
}
bool descriptionHasAcceptableInput() const
{
return !q->description().contains(QLatin1String{"\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 + QLatin1String{", "} + 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();
}
};
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(
xi18n("<para>You are about to revoke the following key:<nl/>%1</para>")
.arg(Formatting::summaryLine(key)));
}
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
GpgME::RevocationReason RevokeKeyDialog::reason() const
{
return static_cast<RevocationReason>(d->reasonGroup.checkedId());
}
#endif
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"
diff --git a/src/utils/accessibility.cpp b/src/utils/accessibility.cpp
new file mode 100644
index 000000000..442fc5ca1
--- /dev/null
+++ b/src/utils/accessibility.cpp
@@ -0,0 +1,33 @@
+/* utils/accessibility.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2022 g10 Code GmbH
+ SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <config-kleopatra.h>
+
+#include "accessibility.h"
+
+#include <KLocalizedString>
+
+#include <QAccessible>
+#include <QObject>
+
+QString Kleo::getAccessibleName(QObject *object)
+{
+ QString name;
+ if (const auto *const iface = QAccessible::queryAccessibleInterface(object)) {
+ name = iface->text(QAccessible::Name);
+ }
+ return name;
+}
+
+QString Kleo::invalidEntryText()
+{
+ return i18nc("text for screen readers to indicate that the associated object, "
+ "such as a form field, has an error",
+ "invalid entry");
+}
diff --git a/src/utils/accessibility.h b/src/utils/accessibility.h
new file mode 100644
index 000000000..c3a096661
--- /dev/null
+++ b/src/utils/accessibility.h
@@ -0,0 +1,18 @@
+/* utils/accessibility.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2022 g10 Code GmbH
+ SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#pragma once
+
+class QObject;
+class QString;
+
+namespace Kleo
+{
+ QString getAccessibleName(QObject *object);
+ QString invalidEntryText();
+}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 16, 4:46 AM (20 h, 44 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
49/97/93e5c1a00f3d17bcd8eadd39e47b

Event Timeline