Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/lookupcertificatescommand.cpp b/src/commands/lookupcertificatescommand.cpp
index 4e61fd579..ff14ba2b9 100644
--- a/src/commands/lookupcertificatescommand.cpp
+++ b/src/commands/lookupcertificatescommand.cpp
@@ -1,652 +1,658 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/lookupcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008, 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "lookupcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "detailscommand.h"
#include <settings.h>
#include "view/tabwidget.h"
#include <Libkleo/Compat>
#include <Libkleo/Debug>
#include <Libkleo/GnuPG>
#include <dialogs/lookupcertificatesdialog.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <QGpgME/Debug>
#include <QGpgME/ImportFromKeyserverJob>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QGpgME/WKDLookupJob>
#include <QGpgME/WKDLookupResult>
#include <gpgme++/data.h>
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <QProgressDialog>
#include <QRegularExpression>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace QGpgME;
class LookupCertificatesCommand::Private : public ImportCertificatesCommand::Private
{
friend class ::Kleo::Commands::LookupCertificatesCommand;
LookupCertificatesCommand *q_func() const
{
return static_cast<LookupCertificatesCommand *>(q);
}
public:
explicit Private(LookupCertificatesCommand *qq, KeyListController *c);
~Private() override;
void init();
private:
void slotSearchTextChanged(const QString &str);
void slotNextKey(const Key &key);
void slotKeyListResult(const KeyListResult &result);
void slotWKDLookupResult(const WKDLookupResult &result);
void tryToFinishKeyLookup();
void slotImportRequested(const std::vector<Key> &keys);
void slotDetailsRequested(const Key &key);
void slotSaveAsRequested(const std::vector<Key> &keys);
void slotDialogRejected()
{
canceled();
}
private:
using ImportCertificatesCommand::Private::showError;
void showError(QWidget *parent, const KeyListResult &result);
void showResult(QWidget *parent, const KeyListResult &result);
void createDialog();
KeyListJob *createKeyListJob(GpgME::Protocol proto) const
{
const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
return cbp ? cbp->keyListJob(true) : nullptr;
}
WKDLookupJob *createWKDLookupJob() const
{
const auto cbp = QGpgME::openpgp();
return cbp ? cbp->wkdLookupJob() : nullptr;
}
ImportFromKeyserverJob *createImportJob(GpgME::Protocol proto) const
{
const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
return cbp ? cbp->importFromKeyserverJob() : nullptr;
}
void startKeyListJob(GpgME::Protocol proto, const QString &str);
void startWKDLookupJob(const QString &str);
bool checkConfig() const;
QWidget *dialogOrParentWidgetOrView() const
{
if (dialog) {
return dialog;
} else {
return parentWidgetOrView();
}
}
void cancelLookup();
void cancelJob(QPointer<Job> &job);
private:
GpgME::Protocol protocol = GpgME::UnknownProtocol;
QString query;
bool autoStartLookup = false;
QPointer<LookupCertificatesDialog> dialog;
QPointer<QProgressDialog> progress;
struct KeyListingVariables {
QPointer<Job> cms;
QPointer<Job> openpgp;
QPointer<Job> wkdJob;
QString pattern;
KeyListResult result;
- std::vector<Key> keys;
- std::map<QString, GpgME::Key::Origin> origins;
+ std::vector<std::pair<Key, GpgME::Key::Origin>> keys;
int numKeysWithoutUserId = 0;
std::set<std::string> wkdKeyFingerprints;
QByteArray wkdKeyData;
QString wkdSource;
bool cmsKeysHaveNoFingerprints = false;
bool openPgpKeysHaveNoFingerprints = false;
void reset()
{
*this = KeyListingVariables();
}
} keyListing;
};
LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
LookupCertificatesCommand::Private::Private(LookupCertificatesCommand *qq, KeyListController *c)
: ImportCertificatesCommand::Private(qq, c)
, dialog()
{
if (!Settings{}.cmsEnabled()) {
protocol = GpgME::OpenPGP;
}
}
LookupCertificatesCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
delete dialog;
}
LookupCertificatesCommand::LookupCertificatesCommand(KeyListController *c)
: ImportCertificatesCommand(new Private(this, c))
{
d->init();
}
LookupCertificatesCommand::LookupCertificatesCommand(const QString &query, KeyListController *c)
: ImportCertificatesCommand(new Private(this, c))
{
d->init();
d->query = query;
d->autoStartLookup = true;
}
LookupCertificatesCommand::LookupCertificatesCommand(QAbstractItemView *v, KeyListController *c)
: ImportCertificatesCommand(v, new Private(this, c))
{
d->init();
if (c->tabWidget()) {
d->query = c->tabWidget()->stringFilter();
// do not start the lookup automatically to prevent unwanted leaking
// of information
}
}
void LookupCertificatesCommand::Private::init()
{
}
LookupCertificatesCommand::~LookupCertificatesCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void LookupCertificatesCommand::setProtocol(GpgME::Protocol protocol)
{
d->protocol = protocol;
}
GpgME::Protocol LookupCertificatesCommand::protocol() const
{
return d->protocol;
}
void LookupCertificatesCommand::doStart()
{
if (!d->checkConfig()) {
d->finished();
return;
}
d->createDialog();
Q_ASSERT(d->dialog);
// if we have a prespecified query, load it into find field
// and start the search, if auto-start is enabled
if (!d->query.isEmpty()) {
d->dialog->setSearchText(d->query);
if (d->autoStartLookup) {
d->slotSearchTextChanged(d->query);
}
} else {
d->dialog->setPassive(false);
}
d->dialog->show();
}
void LookupCertificatesCommand::Private::createDialog()
{
if (dialog) {
return;
}
dialog = new LookupCertificatesDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
const bool wkdOnly = !haveKeyserverConfigured() && !haveX509DirectoryServerConfigured();
dialog->setQueryMode(wkdOnly ? LookupCertificatesDialog::EmailQuery : LookupCertificatesDialog::AnyQuery);
connect(dialog, &LookupCertificatesDialog::searchTextChanged, q, [this](const QString &text) {
slotSearchTextChanged(text);
});
using CertsVec = std::vector<GpgME::Key>;
connect(dialog, &LookupCertificatesDialog::saveAsRequested, q, [this](const CertsVec &certs) {
slotSaveAsRequested(certs);
});
connect(dialog, &LookupCertificatesDialog::importRequested, q, [this](const CertsVec &certs) {
slotImportRequested(certs);
});
connect(dialog, &LookupCertificatesDialog::detailsRequested, q, [this](const GpgME::Key &gpgKey) {
slotDetailsRequested(gpgKey);
});
connect(dialog, &QDialog::rejected, q, [this]() {
slotDialogRejected();
});
}
static auto searchTextToEmailAddress(const QString &s)
{
return QString::fromStdString(UserID::addrSpecFromString(s.toStdString().c_str()));
}
void LookupCertificatesCommand::Private::slotSearchTextChanged(const QString &str)
{
// pressing return might trigger both search and dialog destruction (search focused and default key set)
// On Windows, the dialog is then destroyed before this slot is called
if (dialog) { // thus test
dialog->setOverlayText({});
dialog->setPassive(true);
dialog->setCertificates(std::vector<Key>(), {});
}
keyListing.reset();
keyListing.pattern = str;
if (protocol != GpgME::OpenPGP) {
startKeyListJob(CMS, str);
}
if (protocol != GpgME::CMS) {
static const QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1StringView("[0-9a-fA-F]{6,}")));
if (rx.match(str).hasMatch()) {
qCDebug(KLEOPATRA_LOG) << "Adding 0x prefix to query" << str;
startKeyListJob(OpenPGP, QLatin1StringView{"0x"} + str);
} else {
startKeyListJob(OpenPGP, str);
}
if (str.contains(QLatin1Char{'@'}) && !searchTextToEmailAddress(str).isEmpty()) {
startWKDLookupJob(str);
}
}
const auto jobCount = int(!keyListing.cms.isNull()) + int(!keyListing.openpgp.isNull()) + int(!keyListing.wkdJob.isNull());
if (jobCount > 0) {
progress = new QProgressDialog{dialog};
progress->setAttribute(Qt::WA_DeleteOnClose);
progress->setLabelText(i18nc("@info", "Searching for matching certificates ..."));
progress->setMaximum(jobCount);
progress->setMinimumDuration(0);
progress->setValue(0);
connect(progress, &QProgressDialog::canceled, q, [this]() {
cancelLookup();
});
}
}
void LookupCertificatesCommand::Private::startKeyListJob(GpgME::Protocol proto, const QString &str)
{
if ((proto == GpgME::OpenPGP) && !haveKeyserverConfigured()) {
// avoid starting an OpenPGP key server lookup if key server usage has been disabled;
// for S/MIME we start the job regardless of configured directory servers to account for
// dirmngr knowing better than our check for directory servers
return;
}
KeyListJob *const klj = createKeyListJob(proto);
if (!klj) {
return;
}
connect(klj, &QGpgME::KeyListJob::result, q, [this](const GpgME::KeyListResult &result) {
slotKeyListResult(result);
});
connect(klj, &QGpgME::KeyListJob::nextKey, q, [this](const GpgME::Key &key) {
slotNextKey(key);
});
if (const Error err = klj->start(QStringList(str))) {
keyListing.result.mergeWith(KeyListResult(err));
} else if (proto == CMS) {
keyListing.cms = klj;
} else {
keyListing.openpgp = klj;
}
}
void LookupCertificatesCommand::Private::startWKDLookupJob(const QString &str)
{
const auto job = createWKDLookupJob();
if (!job) {
qCDebug(KLEOPATRA_LOG) << "Failed to create WKDLookupJob";
return;
}
connect(job, &WKDLookupJob::result, q, [this](const WKDLookupResult &result) {
slotWKDLookupResult(result);
});
if (const Error err = job->start(str)) {
keyListing.result.mergeWith(KeyListResult{err});
} else {
keyListing.wkdJob = job;
}
}
void LookupCertificatesCommand::Private::slotNextKey(const Key &key)
{
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring null key";
} else if (!key.primaryFingerprint()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without fingerprint" << key;
if (q->sender() == keyListing.cms) {
keyListing.cmsKeysHaveNoFingerprints = true;
} else if (q->sender() == keyListing.openpgp) {
keyListing.openPgpKeysHaveNoFingerprints = true;
}
} else if (key.numUserIDs() == 0) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without user IDs" << key;
keyListing.numKeysWithoutUserId++;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "got key" << key;
- keyListing.keys.push_back(key);
- keyListing.origins[QString::fromLatin1(key.primaryFingerprint())] = Key::OriginKS;
+ keyListing.keys.push_back({key, Key::OriginKS});
}
}
void LookupCertificatesCommand::Private::slotKeyListResult(const KeyListResult &r)
{
if (q->sender() == keyListing.cms) {
keyListing.cms = nullptr;
} else if (q->sender() == keyListing.openpgp) {
keyListing.openpgp = nullptr;
} else {
qCDebug(KLEOPATRA_LOG) << "unknown sender()" << q->sender();
}
keyListing.result.mergeWith(r);
tryToFinishKeyLookup();
}
static auto removeKeysNotMatchingEmail(const std::vector<Key> &keys, const std::string &email)
{
std::vector<Key> filteredKeys;
const auto addrSpec = UserID::addrSpecFromString(email.c_str());
std::copy_if(std::begin(keys), std::end(keys), std::back_inserter(filteredKeys), [addrSpec](const auto &key) {
const auto uids = key.userIDs();
return std::any_of(std::begin(uids), std::end(uids), [addrSpec](const auto &uid) {
return uid.addrSpec() == addrSpec;
});
});
return filteredKeys;
}
void LookupCertificatesCommand::Private::slotWKDLookupResult(const WKDLookupResult &result)
{
if (q->sender() == keyListing.wkdJob) {
keyListing.wkdJob = nullptr;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "unknown sender()" << q->sender();
}
// we do not want to bother the user with errors during the WKD lookup;
// therefore, we log the result, but we do not merge it into keyListing.result
qCDebug(KLEOPATRA_LOG) << "Result of WKD lookup:" << result.error();
const auto keys = removeKeysNotMatchingEmail(result.keyData().toKeys(GpgME::OpenPGP), result.pattern());
if (!keys.empty()) {
keyListing.wkdKeyData = QByteArray::fromStdString(result.keyData().toString());
keyListing.wkdSource = QString::fromStdString(result.source());
- std::copy(std::begin(keys), std::end(keys), std::back_inserter(keyListing.keys));
for (const auto &key : keys) {
- keyListing.origins[QString::fromLatin1(key.primaryFingerprint())] = Key::OriginWKD;
+ keyListing.keys.push_back({key, Key::OriginWKD});
}
// remember the keys retrieved via WKD for import
std::transform(std::begin(keys),
std::end(keys),
std::inserter(keyListing.wkdKeyFingerprints, std::begin(keyListing.wkdKeyFingerprints)),
[](const auto &k) {
return k.primaryFingerprint();
});
}
tryToFinishKeyLookup();
}
namespace
{
void showKeysWithoutFingerprintsNotification(QWidget *parent, GpgME::Protocol protocol)
{
if (protocol != GpgME::CMS && protocol != GpgME::OpenPGP) {
return;
}
QString message;
if (protocol == GpgME::CMS) {
message = xi18nc("@info",
"<para>One of the X.509 directory services returned certificates without "
"fingerprints. Those certificates are ignored because fingerprints "
"are required as unique identifiers for certificates.</para>"
"<para>You may want to configure a different X.509 directory service "
"in the configuration dialog.</para>");
} else {
message = xi18nc("@info",
"<para>The OpenPGP keyserver returned certificates without "
"fingerprints. Those certificates are ignored because fingerprints "
"are required as unique identifiers for certificates.</para>"
"<para>You may want to configure a different OpenPGP keyserver "
"in the configuration dialog.</para>");
}
KMessageBox::information(parent, message, i18nc("@title", "Invalid Server Reply"), QStringLiteral("certificates-lookup-missing-fingerprints"));
}
}
void LookupCertificatesCommand::Private::tryToFinishKeyLookup()
{
if (progress) {
progress->setValue(progress->value() + 1);
}
if (keyListing.cms || keyListing.openpgp || keyListing.wkdJob) {
// still waiting for jobs to complete
return;
}
if (progress) {
progress->setValue(progress->maximum());
}
if (keyListing.result.error() && !keyListing.result.error().isCanceled() && (keyListing.result.error().code() != GPG_ERR_NOT_FOUND)) {
showError(dialog, keyListing.result);
}
if (keyListing.result.isTruncated()) {
showResult(dialog, keyListing.result);
}
if (keyListing.cmsKeysHaveNoFingerprints) {
showKeysWithoutFingerprintsNotification(dialog, GpgME::CMS);
}
if (keyListing.openPgpKeysHaveNoFingerprints) {
showKeysWithoutFingerprintsNotification(dialog, GpgME::OpenPGP);
}
if (dialog) {
dialog->setPassive(false);
- dialog->setCertificates(keyListing.keys, keyListing.origins);
+
+ std::sort(keyListing.keys.begin(), keyListing.keys.end(), [](const auto &lhs, const auto &rhs) {
+ return qstricmp(lhs.first.primaryFingerprint(), rhs.first.primaryFingerprint());
+ });
+ std::vector<Key> keys;
+ std::vector<Key::Origin> origins;
+ for (const auto &pair : keyListing.keys) {
+ keys.push_back(pair.first), origins.push_back(pair.second);
+ }
+ dialog->setCertificates(keys, origins);
if (keyListing.keys.size() == 0) {
dialog->setOverlayText(i18nc("@info", "No certificates found"));
}
if (keyListing.numKeysWithoutUserId > 0) {
qCDebug(KLEOPATRA_LOG) << keyListing.numKeysWithoutUserId << "certificates without user IDs were ignored";
}
} else {
finished();
}
}
void LookupCertificatesCommand::Private::slotImportRequested(const std::vector<Key> &keys)
{
dialog = nullptr;
Q_ASSERT(!keys.empty());
Q_ASSERT(std::none_of(keys.cbegin(), keys.cend(), [](const Key &key) {
return key.isNull();
}));
std::vector<Key> wkdKeys, otherKeys;
otherKeys.reserve(keys.size());
kdtools::separate_if(std::begin(keys), std::end(keys), std::back_inserter(wkdKeys), std::back_inserter(otherKeys), [this](const auto &key) {
return key.primaryFingerprint() && keyListing.wkdKeyFingerprints.find(key.primaryFingerprint()) != std::end(keyListing.wkdKeyFingerprints);
});
std::vector<Key> pgp, cms;
pgp.reserve(otherKeys.size());
cms.reserve(otherKeys.size());
kdtools::separate_if(otherKeys.begin(), otherKeys.end(), std::back_inserter(pgp), std::back_inserter(cms), [](const Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
setWaitForMoreJobs(true);
if (!wkdKeys.empty()) {
// set an import filter, so that only user IDs matching the email address used for the WKD lookup are imported
const QString importFilter = QLatin1StringView{"keep-uid=mbox = "} + searchTextToEmailAddress(keyListing.pattern);
startImport(OpenPGP, keyListing.wkdKeyData, keyListing.wkdSource, {importFilter, Key::OriginWKD, keyListing.wkdSource});
}
if (!pgp.empty()) {
startImport(OpenPGP, pgp, i18nc(R"(@title %1:"OpenPGP" or "S/MIME")", "%1 Certificate Server", Formatting::displayName(OpenPGP)));
}
if (!cms.empty()) {
startImport(CMS, cms, i18nc(R"(@title %1:"OpenPGP" or "S/MIME")", "%1 Certificate Server", Formatting::displayName(CMS)));
}
setWaitForMoreJobs(false);
}
void LookupCertificatesCommand::Private::slotSaveAsRequested(const std::vector<Key> &keys)
{
Q_UNUSED(keys)
qCDebug(KLEOPATRA_LOG) << "not implemented";
}
void LookupCertificatesCommand::Private::slotDetailsRequested(const Key &key)
{
Command *const cmd = new DetailsCommand(key);
cmd->setParentWidget(dialogOrParentWidgetOrView());
cmd->start();
}
void LookupCertificatesCommand::Private::cancelLookup()
{
cancelJob(keyListing.cms);
cancelJob(keyListing.openpgp);
cancelJob(keyListing.wkdJob);
if (dialog) {
dialog->setPassive(false);
} else {
finished();
}
}
void LookupCertificatesCommand::Private::cancelJob(QPointer<Job> &job)
{
if (job) {
disconnect(job.data(), nullptr, q, nullptr);
job->slotCancel();
job.clear();
}
}
void LookupCertificatesCommand::doCancel()
{
ImportCertificatesCommand::doCancel();
if (QDialog *const dlg = d->dialog) {
d->dialog = nullptr;
dlg->close();
}
}
void LookupCertificatesCommand::Private::showError(QWidget *parent, const KeyListResult &result)
{
if (!result.error()) {
return;
}
KMessageBox::information(parent,
i18nc("@info", "Failed to search on certificate server. The error returned was:\n%1", Formatting::errorAsString(result.error())));
}
void LookupCertificatesCommand::Private::showResult(QWidget *parent, const KeyListResult &result)
{
if (result.isTruncated())
KMessageBox::information(parent,
xi18nc("@info",
"<para>The query result has been truncated.</para>"
"<para>Either the local or a remote limit on "
"the maximum number of returned hits has "
"been exceeded.</para>"
"<para>You can try to increase the local limit "
"in the configuration dialog, but if one "
"of the configured servers is the limiting "
"factor, you have to refine your search.</para>"),
i18nc("@title", "Result Truncated"),
QStringLiteral("lookup-certificates-truncated-result"));
}
bool LookupCertificatesCommand::Private::checkConfig() const
{
// unless CMS-only lookup is requested we always try a lookup via WKD
const bool ok = (protocol != GpgME::CMS) || haveX509DirectoryServerConfigured();
if (!ok) {
information(xi18nc("@info",
"<para>You do not have any directory servers configured.</para>"
"<para>You need to configure at least one directory server to "
"search on one.</para>"
"<para>You can configure directory servers here: "
"<interface>Settings->Configure Kleopatra</interface>.</para>"),
i18nc("@title", "No Directory Servers Configured"));
}
return ok;
}
#undef d
#undef q
#include "moc_lookupcertificatescommand.cpp"
diff --git a/src/dialogs/lookupcertificatesdialog.cpp b/src/dialogs/lookupcertificatesdialog.cpp
index 20179f681..fe82f03d9 100644
--- a/src/dialogs/lookupcertificatesdialog.cpp
+++ b/src/dialogs/lookupcertificatesdialog.cpp
@@ -1,421 +1,421 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/lookupcertificatesdialog.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 <config-kleopatra.h>
#include "lookupcertificatesdialog.h"
#include <view/keytreeview.h>
#include <view/textoverlay.h>
#include <kleopatra_debug.h>
#include <Libkleo/KeyListModel>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QTreeView>
#include <QVBoxLayout>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
class LookupCertificatesDialog::Private
{
friend class ::Kleo::Dialogs::LookupCertificatesDialog;
LookupCertificatesDialog *const q;
public:
explicit Private(LookupCertificatesDialog *qq);
~Private();
private:
void slotSelectionChanged()
{
enableDisableWidgets();
}
void slotSearchTextChanged()
{
enableDisableWidgets();
}
void slotSearchClicked()
{
Q_EMIT q->searchTextChanged(searchText());
}
void slotDetailsClicked()
{
Q_ASSERT(q->selectedCertificates().size() == 1);
Q_EMIT q->detailsRequested(q->selectedCertificates().front());
}
void slotSaveAsClicked()
{
Q_EMIT q->saveAsRequested(q->selectedCertificates());
}
void readConfig();
void writeConfig();
void enableDisableWidgets();
QString searchText() const
{
return ui.findED->text().trimmed();
}
std::vector<Key> selectedCertificates() const
{
const QAbstractItemView *const view = ui.resultTV->view();
if (!view) {
return std::vector<Key>();
}
const auto *const model = dynamic_cast<KeyListModelInterface *>(view->model());
Q_ASSERT(model);
const QItemSelectionModel *const sm = view->selectionModel();
Q_ASSERT(sm);
return model->keys(sm->selectedRows());
}
int numSelectedCertificates() const
{
return ui.resultTV->selectedKeys().size();
}
QValidator *queryValidator();
void updateQueryMode();
private:
QueryMode queryMode = AnyQuery;
bool passive;
QValidator *anyQueryValidator = nullptr;
QValidator *emailQueryValidator = nullptr;
struct Ui {
QLabel *guidanceLabel;
QLabel *findLB;
QLineEdit *findED;
QPushButton *findPB;
Kleo::KeyTreeView *resultTV;
TextOverlay *overlay;
QPushButton *selectAllPB;
QPushButton *deselectAllPB;
QPushButton *detailsPB;
QPushButton *saveAsPB;
QDialogButtonBox *buttonBox;
void setupUi(QDialog *dialog)
{
auto verticalLayout = new QVBoxLayout{dialog};
auto gridLayout = new QGridLayout{};
int row = 0;
guidanceLabel = new QLabel{dialog};
gridLayout->addWidget(guidanceLabel, row, 0, 1, 3);
row++;
findLB = new QLabel{i18n("Find:"), dialog};
gridLayout->addWidget(findLB, row, 0, 1, 1);
findED = new QLineEdit{dialog};
findLB->setBuddy(findED);
gridLayout->addWidget(findED, row, 1, 1, 1);
findPB = new QPushButton{i18n("Search"), dialog};
findPB->setAutoDefault(false);
gridLayout->addWidget(findPB, row, 2, 1, 1);
row++;
gridLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}, row, 0, 1, 3);
row++;
resultTV = new Kleo::KeyTreeView(dialog);
resultTV->setEnabled(true);
resultTV->setMinimumSize(QSize(400, 0));
overlay = new TextOverlay{resultTV, dialog};
overlay->hide();
gridLayout->addWidget(resultTV, row, 0, 1, 2);
auto buttonLayout = new QVBoxLayout{};
selectAllPB = new QPushButton{i18n("Select All"), dialog};
selectAllPB->setEnabled(false);
selectAllPB->setAutoDefault(false);
buttonLayout->addWidget(selectAllPB);
deselectAllPB = new QPushButton{i18n("Deselect All"), dialog};
deselectAllPB->setEnabled(false);
deselectAllPB->setAutoDefault(false);
buttonLayout->addWidget(deselectAllPB);
buttonLayout->addStretch();
detailsPB = new QPushButton{i18n("Details..."), dialog};
detailsPB->setEnabled(false);
detailsPB->setAutoDefault(false);
buttonLayout->addWidget(detailsPB);
saveAsPB = new QPushButton{i18n("Save As..."), dialog};
saveAsPB->setEnabled(false);
saveAsPB->setAutoDefault(false);
buttonLayout->addWidget(saveAsPB);
gridLayout->addLayout(buttonLayout, row, 2, 1, 1);
verticalLayout->addLayout(gridLayout);
buttonBox = new QDialogButtonBox{dialog};
buttonBox->setStandardButtons(QDialogButtonBox::Close | QDialogButtonBox::Save);
verticalLayout->addWidget(buttonBox);
QObject::connect(findED, SIGNAL(returnPressed()), findPB, SLOT(animateClick()));
QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
QObject::connect(findPB, SIGNAL(clicked()), dialog, SLOT(slotSearchClicked()));
QObject::connect(detailsPB, SIGNAL(clicked()), dialog, SLOT(slotDetailsClicked()));
QObject::connect(saveAsPB, SIGNAL(clicked()), dialog, SLOT(slotSaveAsClicked()));
QObject::connect(findED, SIGNAL(textChanged(QString)), dialog, SLOT(slotSearchTextChanged()));
QMetaObject::connectSlotsByName(dialog);
}
explicit Ui(LookupCertificatesDialog *q)
{
q->setWindowTitle(i18nc("@title:window", "Lookup on Server"));
setupUi(q);
saveAsPB->hide(); // ### not yet implemented in LookupCertificatesCommand
findED->setClearButtonEnabled(true);
resultTV->setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q));
resultTV->setHierarchicalView(false);
importPB()->setText(i18n("Import"));
importPB()->setEnabled(false);
connect(resultTV->view(), SIGNAL(doubleClicked(QModelIndex)), q, SLOT(slotDetailsClicked()));
findED->setFocus();
connect(selectAllPB, &QPushButton::clicked, resultTV->view(), &QTreeView::selectAll);
connect(deselectAllPB, &QPushButton::clicked, resultTV->view(), &QTreeView::clearSelection);
}
QPushButton *importPB() const
{
return buttonBox->button(QDialogButtonBox::Save);
}
QPushButton *closePB() const
{
return buttonBox->button(QDialogButtonBox::Close);
}
} ui;
};
LookupCertificatesDialog::Private::Private(LookupCertificatesDialog *qq)
: q(qq)
, passive(false)
, ui(q)
{
connect(ui.resultTV->view()->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), q, SLOT(slotSelectionChanged()));
updateQueryMode();
}
LookupCertificatesDialog::Private::~Private()
{
}
void LookupCertificatesDialog::Private::readConfig()
{
KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
KConfigGroup resultKeysConfig = configGroup.group(QStringLiteral("ResultKeysView"));
ui.resultTV->restoreLayout(resultKeysConfig);
const QSize size = configGroup.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
q->resize(size);
}
}
void LookupCertificatesDialog::Private::writeConfig()
{
KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
configGroup.writeEntry("Size", q->size());
configGroup.sync();
}
static QString guidanceText(LookupCertificatesDialog::QueryMode mode)
{
switch (mode) {
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << mode;
[[fallthrough]];
case LookupCertificatesDialog::AnyQuery:
return xi18nc("@info", "Enter a search term to search for matching certificates.");
case LookupCertificatesDialog::EmailQuery:
return xi18nc("@info", "Enter an email address to search for matching certificates.");
};
}
QValidator *LookupCertificatesDialog::Private::queryValidator()
{
switch (queryMode) {
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << queryMode;
[[fallthrough]];
case AnyQuery: {
if (!anyQueryValidator) {
// allow any query with at least one non-whitespace character
anyQueryValidator = new QRegularExpressionValidator{QRegularExpression{QStringLiteral(".*\\S+.*")}, q};
}
return anyQueryValidator;
}
case EmailQuery: {
if (!emailQueryValidator) {
// allow anything that looks remotely like an email address, i.e.
// anything with an '@' surrounded by non-whitespace characters
const QRegularExpression simpleEmailRegex{QStringLiteral(".*\\S+@\\S+.*")};
emailQueryValidator = new QRegularExpressionValidator{simpleEmailRegex, q};
}
return emailQueryValidator;
}
}
}
void LookupCertificatesDialog::Private::updateQueryMode()
{
ui.guidanceLabel->setText(guidanceText(queryMode));
ui.findED->setValidator(queryValidator());
}
LookupCertificatesDialog::LookupCertificatesDialog(QWidget *p, Qt::WindowFlags f)
: QDialog(p, f)
, d(new Private(this))
{
d->ui.findPB->setEnabled(false);
d->readConfig();
}
LookupCertificatesDialog::~LookupCertificatesDialog()
{
d->writeConfig();
}
void LookupCertificatesDialog::setQueryMode(QueryMode mode)
{
d->queryMode = mode;
d->updateQueryMode();
}
LookupCertificatesDialog::QueryMode LookupCertificatesDialog::queryMode() const
{
return d->queryMode;
}
-void LookupCertificatesDialog::setCertificates(const std::vector<Key> &certs, const std::map<QString, Key::Origin> &origins)
+void LookupCertificatesDialog::setCertificates(const std::vector<Key> &certs, const std::vector<Key::Origin> &origins)
{
d->ui.resultTV->view()->setFocus();
d->ui.resultTV->setKeys(certs, origins);
if (certs.size() == 1) {
d->ui.resultTV->view()->setCurrentIndex(d->ui.resultTV->view()->model()->index(0, 0));
}
}
std::vector<Key> LookupCertificatesDialog::selectedCertificates() const
{
return d->selectedCertificates();
}
void LookupCertificatesDialog::setPassive(bool on)
{
if (d->passive == on) {
return;
}
d->passive = on;
d->enableDisableWidgets();
}
bool LookupCertificatesDialog::isPassive() const
{
return d->passive;
}
void LookupCertificatesDialog::setSearchText(const QString &text)
{
d->ui.findED->setText(text);
}
QString LookupCertificatesDialog::searchText() const
{
return d->ui.findED->text();
}
void LookupCertificatesDialog::setOverlayText(const QString &text)
{
if (text.isEmpty()) {
d->ui.overlay->hideOverlay();
} else {
d->ui.overlay->setText(text);
d->ui.overlay->showOverlay();
}
d->ui.selectAllPB->setEnabled(text.isEmpty());
d->ui.deselectAllPB->setEnabled(text.isEmpty());
}
QString LookupCertificatesDialog::overlayText() const
{
return d->ui.overlay->text();
}
void LookupCertificatesDialog::accept()
{
Q_ASSERT(!selectedCertificates().empty());
Q_EMIT importRequested(selectedCertificates());
QDialog::accept();
}
void LookupCertificatesDialog::Private::enableDisableWidgets()
{
// enable/disable everything except 'close', based on passive:
const QList<QObject *> list = q->children();
for (QObject *const o : list) {
if (QWidget *const w = qobject_cast<QWidget *>(o)) {
w->setDisabled(passive && w != ui.closePB() && w != ui.buttonBox);
}
}
if (passive) {
return;
}
q->setOverlayText({});
ui.findPB->setEnabled(ui.findED->hasAcceptableInput());
const int n = q->selectedCertificates().size();
ui.detailsPB->setEnabled(n == 1);
ui.saveAsPB->setEnabled(n == 1);
ui.importPB()->setEnabled(n != 0);
ui.importPB()->setDefault(false); // otherwise Import becomes default button if enabled and return triggers both a search and accept()
}
#include "moc_lookupcertificatesdialog.cpp"
diff --git a/src/dialogs/lookupcertificatesdialog.h b/src/dialogs/lookupcertificatesdialog.h
index c91b90a33..a1ccaae17 100644
--- a/src/dialogs/lookupcertificatesdialog.h
+++ b/src/dialogs/lookupcertificatesdialog.h
@@ -1,71 +1,71 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/lookupcertificatesdialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDialog>
#include <utils/pimpl_ptr.h>
#include <vector>
#include <gpgme++/key.h>
namespace Kleo
{
namespace Dialogs
{
class LookupCertificatesDialog : public QDialog
{
Q_OBJECT
public:
enum QueryMode {
AnyQuery, //< any query is allowed
EmailQuery, //< only email queries are allowed
};
explicit LookupCertificatesDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~LookupCertificatesDialog() override;
void setQueryMode(QueryMode mode);
QueryMode queryMode() const;
- void setCertificates(const std::vector<GpgME::Key> &certs, const std::map<QString, GpgME::Key::Origin> &origins);
+ void setCertificates(const std::vector<GpgME::Key> &certs, const std::vector<GpgME::Key::Origin> &origins);
std::vector<GpgME::Key> selectedCertificates() const;
void setPassive(bool passive);
bool isPassive() const;
void setSearchText(const QString &text);
QString searchText() const;
void setOverlayText(const QString &text);
QString overlayText() const;
Q_SIGNALS:
void searchTextChanged(const QString &text);
void saveAsRequested(const std::vector<GpgME::Key> &certs);
void importRequested(const std::vector<GpgME::Key> &certs);
void detailsRequested(const GpgME::Key &certs);
public Q_SLOTS:
void accept() override;
private:
class Private;
kdtools::pimpl_ptr<Private> d;
Q_PRIVATE_SLOT(d, void slotSearchTextChanged())
Q_PRIVATE_SLOT(d, void slotSearchClicked())
Q_PRIVATE_SLOT(d, void slotSelectionChanged())
Q_PRIVATE_SLOT(d, void slotDetailsClicked())
Q_PRIVATE_SLOT(d, void slotSaveAsClicked())
};
}
}
diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp
index 4eebaa920..e6d6c8ce4 100644
--- a/src/view/keytreeview.cpp
+++ b/src/view/keytreeview.cpp
@@ -1,673 +1,676 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/keytreeview.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 <config-kleopatra.h>
#include "keytreeview.h"
#include "searchbar.h"
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeyRearrangeColumnsProxyModel>
#include <Libkleo/Predicates>
#include <Libkleo/TreeView>
#include "utils/headerview.h"
#include "utils/tags.h"
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/Stl_Util>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
#include <QAction>
#include <QContextMenuEvent>
#include <QEvent>
#include <QHeaderView>
#include <QItemSelection>
#include <QItemSelectionModel>
#include <QLayout>
#include <QList>
#include <QMenu>
#include <QTimer>
#include <KLocalizedString>
#include <KSharedConfig>
static int tagsColumn;
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
class TreeViewInternal : public Kleo::TreeView
{
public:
explicit TreeViewInternal(QWidget *parent = nullptr)
: Kleo::TreeView{parent}
{
connect(this, &TreeView::columnEnabled, this, [this](int column) {
if (column == tagsColumn) {
Tags::enableTags();
}
auto tv = qobject_cast<KeyTreeView *>(this->parent());
if (tv) {
tv->resizeColumns();
}
});
connect(this, &TreeView::columnDisabled, this, [this]() {
auto tv = qobject_cast<KeyTreeView *>(this->parent());
if (tv) {
tv->resizeColumns();
}
});
}
QSize minimumSizeHint() const override
{
const QSize min = QTreeView::minimumSizeHint();
return QSize(min.width(), min.height() + 5 * fontMetrics().height());
}
protected:
void focusInEvent(QFocusEvent *event) override
{
QTreeView::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &TreeViewInternal::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
private:
QMenu *mHeaderPopup = nullptr;
QList<QAction *> mColumnActions;
};
const KeyListModelInterface *keyListModel(const QTreeView &view)
{
const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(view.model());
Q_ASSERT(klmi);
return klmi;
}
} // anon namespace
KeyTreeView::KeyTreeView(QWidget *parent)
: QWidget(parent)
, m_proxy(new KeyListSortFilterProxyModel(this))
, m_additionalProxy(nullptr)
, m_view(new TreeViewInternal(this))
, m_flatModel(nullptr)
, m_hierarchicalModel(nullptr)
, m_stringFilter()
, m_keyFilter()
, m_isHierarchical(true)
{
init();
}
KeyTreeView::KeyTreeView(const KeyTreeView &other)
: QWidget(nullptr)
, m_proxy(new KeyListSortFilterProxyModel(this))
, m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr)
, m_view(new TreeViewInternal(this))
, m_flatModel(other.m_flatModel)
, m_hierarchicalModel(other.m_hierarchicalModel)
, m_stringFilter(other.m_stringFilter)
, m_keyFilter(other.m_keyFilter)
, m_group(other.m_group)
, m_isHierarchical(other.m_isHierarchical)
{
init();
setColumnSizes(other.columnSizes());
setSortColumn(other.sortColumn(), other.sortOrder());
}
KeyTreeView::KeyTreeView(const QString &text,
const std::shared_ptr<KeyFilter> &kf,
AbstractKeyListSortFilterProxyModel *proxy,
QWidget *parent,
const KConfigGroup &group)
: QWidget(parent)
, m_proxy(new KeyListSortFilterProxyModel(this))
, m_additionalProxy(proxy)
, m_view(new TreeViewInternal(this))
, m_flatModel(nullptr)
, m_hierarchicalModel(nullptr)
, m_stringFilter(text)
, m_keyFilter(kf)
, m_group(group)
, m_isHierarchical(true)
, m_onceResized(false)
{
init();
}
void KeyTreeView::setColumnSizes(const std::vector<int> &sizes)
{
if (sizes.empty()) {
return;
}
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
hv->setSectionSizes(sizes);
}
}
void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder)
{
Q_ASSERT(m_view);
m_view->sortByColumn(sortColumn, sortOrder);
}
int KeyTreeView::sortColumn() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
return m_view->header()->sortIndicatorSection();
}
Qt::SortOrder KeyTreeView::sortOrder() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
return m_view->header()->sortIndicatorOrder();
}
std::vector<int> KeyTreeView::columnSizes() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
return hv->sectionSizes();
} else {
return std::vector<int>();
}
}
void KeyTreeView::restoreLayout(const KConfigGroup &group)
{
if (!group.isValid() || !m_view->restoreColumnLayout(group.name())) {
// if config is empty then use default settings
// The numbers have to be in line with the order in
// setsSourceColumns above
m_view->hideColumn(5);
for (int i = 7; i < m_view->model()->columnCount(); ++i) {
m_view->hideColumn(i);
}
if (KeyCache::instance()->initialized()) {
QTimer::singleShot(0, this, &KeyTreeView::resizeColumns);
}
} else {
m_onceResized = true;
}
if (!m_view->isColumnHidden(tagsColumn)) {
Tags::enableTags();
}
}
void KeyTreeView::init()
{
KDAB_SET_OBJECT_NAME(m_proxy);
KDAB_SET_OBJECT_NAME(m_view);
if (m_group.isValid()) {
// Reopen as non const
KConfig *conf = m_group.config();
m_group = conf->group(m_group.name());
}
if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) {
KDAB_SET_OBJECT_NAME(m_additionalProxy);
}
QLayout *layout = new QVBoxLayout(this);
KDAB_SET_OBJECT_NAME(layout);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_view);
auto headerView = new HeaderView(Qt::Horizontal);
KDAB_SET_OBJECT_NAME(headerView);
headerView->installEventFilter(m_view);
headerView->setSectionsMovable(true);
m_view->setHeader(headerView);
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_view->setAllColumnsShowFocus(false);
m_view->setSortingEnabled(true);
m_view->setAccessibleName(i18n("Certificates"));
m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
// we show details on double-click
m_view->setExpandsOnDoubleClick(false);
if (model()) {
if (m_additionalProxy) {
m_additionalProxy->setSourceModel(model());
} else {
m_proxy->setSourceModel(model());
}
}
if (m_additionalProxy) {
m_proxy->setSourceModel(m_additionalProxy);
if (!m_additionalProxy->parent()) {
m_additionalProxy->setParent(this);
}
}
m_proxy->setFilterRegularExpression(QRegularExpression::escape(m_stringFilter));
m_proxy->setKeyFilter(m_keyFilter);
m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
auto rearangingModel = new KeyRearrangeColumnsProxyModel(this);
rearangingModel->setSourceModel(m_proxy);
QList<int> columns = {
KeyList::PrettyName,
KeyList::PrettyEMail,
KeyList::Validity,
KeyList::ValidFrom,
KeyList::ValidUntil,
KeyList::TechnicalDetails,
KeyList::KeyID,
KeyList::Fingerprint,
KeyList::OwnerTrust,
KeyList::Origin,
KeyList::LastUpdate,
KeyList::Issuer,
KeyList::SerialNumber,
KeyList::Remarks,
KeyList::Algorithm,
KeyList::Keygrip,
};
tagsColumn = columns.indexOf(KeyList::Remarks);
rearangingModel->setSourceColumns(columns);
m_view->setModel(rearangingModel);
/* Handle expansion state */
if (m_group.isValid()) {
m_expandedKeys = m_group.readEntry("Expanded", QStringList());
}
connect(m_view, &QTreeView::expanded, this, [this](const QModelIndex &index) {
if (!index.isValid()) {
return;
}
const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
return;
}
const auto fpr = QString::fromLatin1(key.primaryFingerprint());
if (m_expandedKeys.contains(fpr)) {
return;
}
m_expandedKeys << fpr;
if (m_group.isValid()) {
m_group.writeEntry("Expanded", m_expandedKeys);
}
});
connect(m_view, &QTreeView::collapsed, this, [this](const QModelIndex &index) {
if (!index.isValid()) {
return;
}
const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
return;
}
m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint()));
if (m_group.isValid()) {
m_group.writeEntry("Expanded", m_expandedKeys);
}
});
updateModelConnections(nullptr, model());
}
void KeyTreeView::restoreExpandState()
{
if (!KeyCache::instance()->initialized()) {
qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting.";
return;
}
for (const auto &fpr : std::as_const(m_expandedKeys)) {
const KeyListModelInterface *const km = keyListModel(*m_view);
if (!km) {
qCWarning(KLEOPATRA_LOG) << "invalid model";
return;
}
const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData());
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache";
m_expandedKeys.removeAll(fpr);
return;
}
const auto idx = km->index(key);
if (!idx.isValid()) {
qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model";
m_expandedKeys.removeAll(fpr);
return;
}
m_view->expand(idx);
}
}
void KeyTreeView::setUpTagKeys()
{
const auto tagKeys = Tags::tagKeys();
if (m_hierarchicalModel) {
m_hierarchicalModel->setRemarkKeys(tagKeys);
}
if (m_flatModel) {
m_flatModel->setRemarkKeys(tagKeys);
}
}
KeyTreeView::~KeyTreeView() = default;
static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm)
{
Q_ASSERT(pm);
while (auto const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) {
pm = sm;
}
return pm;
}
void KeyTreeView::updateModelConnections(AbstractKeyListModel *oldModel, AbstractKeyListModel *newModel)
{
if (oldModel == newModel) {
return;
}
if (oldModel) {
disconnect(oldModel, &QAbstractItemModel::modelAboutToBeReset, this, &KeyTreeView::saveStateBeforeModelChange);
disconnect(oldModel, &QAbstractItemModel::modelReset, this, &KeyTreeView::restoreStateAfterModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &KeyTreeView::saveStateBeforeModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsInserted, this, &KeyTreeView::restoreStateAfterModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &KeyTreeView::saveStateBeforeModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsRemoved, this, &KeyTreeView::restoreStateAfterModelChange);
}
if (newModel) {
connect(newModel, &QAbstractItemModel::modelAboutToBeReset, this, &KeyTreeView::saveStateBeforeModelChange);
connect(newModel, &QAbstractItemModel::modelReset, this, &KeyTreeView::restoreStateAfterModelChange);
connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &KeyTreeView::saveStateBeforeModelChange);
connect(newModel, &QAbstractItemModel::rowsInserted, this, &KeyTreeView::restoreStateAfterModelChange);
connect(newModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &KeyTreeView::saveStateBeforeModelChange);
connect(newModel, &QAbstractItemModel::rowsRemoved, this, &KeyTreeView::restoreStateAfterModelChange);
}
}
void KeyTreeView::setFlatModel(AbstractKeyListModel *model)
{
if (model == m_flatModel) {
return;
}
auto oldModel = m_flatModel;
m_flatModel = model;
if (!m_isHierarchical)
// TODO: this fails when called after setHierarchicalView( false )...
{
find_last_proxy(m_proxy)->setSourceModel(model);
updateModelConnections(oldModel, model);
}
}
void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == m_hierarchicalModel) {
return;
}
auto oldModel = m_hierarchicalModel;
m_hierarchicalModel = model;
if (m_isHierarchical) {
find_last_proxy(m_proxy)->setSourceModel(model);
updateModelConnections(oldModel, model);
m_view->expandAll();
for (int column = 0; column < m_view->header()->count(); ++column) {
m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column)));
}
}
}
void KeyTreeView::setStringFilter(const QString &filter)
{
if (filter == m_stringFilter) {
return;
}
m_stringFilter = filter;
m_proxy->setFilterRegularExpression(QRegularExpression::escape(filter));
Q_EMIT stringFilterChanged(filter);
}
void KeyTreeView::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) {
return;
}
m_keyFilter = filter;
m_proxy->setKeyFilter(filter);
Q_EMIT keyFilterChanged(filter);
}
namespace
{
QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const QTreeView &view)
{
const QModelIndexList indexes = keyListModel(view)->indexes(keys);
return std::accumulate(indexes.cbegin(), indexes.cend(), QItemSelection(), [](QItemSelection selection, const QModelIndex &index) {
if (index.isValid()) {
selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
}
return selection;
});
}
}
void KeyTreeView::selectKeys(const std::vector<Key> &keys)
{
m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
std::vector<Key> KeyTreeView::selectedKeys() const
{
return keyListModel(*m_view)->keys(m_view->selectionModel()->selectedRows());
}
void KeyTreeView::setHierarchicalView(bool on)
{
if (on == m_isHierarchical) {
return;
}
if (on && !hierarchicalModel()) {
qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set";
return;
}
if (!on && !flatModel()) {
qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set";
return;
}
const std::vector<Key> selectedKeys = this->selectedKeys();
const Key currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
auto oldModel = model();
m_isHierarchical = on;
find_last_proxy(m_proxy)->setSourceModel(model());
updateModelConnections(oldModel, model());
if (on) {
m_view->expandAll();
}
selectKeys(selectedKeys);
if (!currentKey.isNull()) {
const QModelIndex currentIndex = keyListModel(*m_view)->index(currentKey);
if (currentIndex.isValid()) {
m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
m_view->scrollTo(currentIndex);
}
}
m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
Q_EMIT hierarchicalChanged(on);
}
-void KeyTreeView::setKeys(const std::vector<Key> &keys, const std::map<QString, Key::Origin> &origins)
+void KeyTreeView::setKeys(const std::vector<Key> &keys, const std::vector<Key::Origin> &extraOrigins)
{
std::vector<Key> sorted = keys;
- _detail::sort_by_fpr(sorted);
- _detail::remove_duplicates_by_fpr(sorted);
+
+ if (extraOrigins.empty()) {
+ _detail::sort_by_fpr(sorted);
+ _detail::remove_duplicates_by_fpr(sorted);
+ }
m_keys = sorted;
if (m_flatModel) {
- m_flatModel->setKeys(sorted, origins);
+ m_flatModel->setKeys(sorted, extraOrigins);
}
if (m_hierarchicalModel) {
- m_hierarchicalModel->setKeys(sorted, origins);
+ m_hierarchicalModel->setKeys(sorted, extraOrigins);
}
}
void KeyTreeView::addKeysImpl(const std::vector<Key> &keys, bool select)
{
if (keys.empty()) {
return;
}
if (m_keys.empty()) {
setKeys(keys);
return;
}
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
std::vector<Key> newKeys = _detail::union_by_fpr(sorted, m_keys);
m_keys.swap(newKeys);
if (m_flatModel) {
m_flatModel->addKeys(sorted);
}
if (m_hierarchicalModel) {
m_hierarchicalModel->addKeys(sorted);
}
if (select) {
selectKeys(sorted);
}
}
void KeyTreeView::addKeysSelected(const std::vector<Key> &keys)
{
addKeysImpl(keys, true);
}
void KeyTreeView::addKeysUnselected(const std::vector<Key> &keys)
{
addKeysImpl(keys, false);
}
void KeyTreeView::removeKeys(const std::vector<Key> &keys)
{
if (keys.empty()) {
return;
}
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
std::vector<Key> newKeys;
newKeys.reserve(m_keys.size());
std::set_difference(m_keys.begin(), m_keys.end(), sorted.begin(), sorted.end(), std::back_inserter(newKeys), _detail::ByFingerprint<std::less>());
m_keys.swap(newKeys);
if (m_flatModel) {
std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) {
m_flatModel->removeKey(key);
});
}
if (m_hierarchicalModel) {
std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) {
m_hierarchicalModel->removeKey(key);
});
}
}
void KeyTreeView::disconnectSearchBar()
{
for (const auto &connection : m_connections) {
disconnect(connection);
}
m_connections.clear();
}
bool KeyTreeView::connectSearchBar(const SearchBar *bar)
{
m_connections.reserve(4);
m_connections.push_back(connect(this, &KeyTreeView::stringFilterChanged, bar, &SearchBar::setStringFilter));
m_connections.push_back(connect(bar, &SearchBar::stringFilterChanged, this, &KeyTreeView::setStringFilter));
m_connections.push_back(connect(this, &KeyTreeView::keyFilterChanged, bar, &SearchBar::setKeyFilter));
m_connections.push_back(connect(bar, &SearchBar::keyFilterChanged, this, &KeyTreeView::setKeyFilter));
return std::all_of(m_connections.cbegin(), m_connections.cend(), [](const QMetaObject::Connection &conn) {
return conn;
});
}
void KeyTreeView::resizeColumns()
{
m_view->setColumnWidth(KeyList::PrettyName, 260);
m_view->setColumnWidth(KeyList::PrettyEMail, 260);
for (int i = 2; i < m_view->model()->columnCount(); ++i) {
m_view->resizeColumnToContents(i);
}
}
void KeyTreeView::saveStateBeforeModelChange()
{
m_currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
m_selectedKeys = selectedKeys();
}
void KeyTreeView::restoreStateAfterModelChange()
{
restoreExpandState();
selectKeys(m_selectedKeys);
if (!m_currentKey.isNull()) {
const QModelIndex currentIndex = keyListModel(*m_view)->index(m_currentKey);
if (currentIndex.isValid()) {
m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
m_view->scrollTo(currentIndex);
}
}
setUpTagKeys();
if (!m_onceResized) {
m_onceResized = true;
resizeColumns();
}
}
#include "moc_keytreeview.cpp"
diff --git a/src/view/keytreeview.h b/src/view/keytreeview.h
index eb0331e1a..22ac56003 100644
--- a/src/view/keytreeview.h
+++ b/src/view/keytreeview.h
@@ -1,168 +1,169 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/keytreeview.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QWidget>
#include <QString>
#include <QStringList>
#include <gpgme++/key.h>
#include <memory>
#include <vector>
#include <KConfigGroup>
#include <Libkleo/TreeView>
class QTreeView;
namespace Kleo
{
class KeyFilter;
class AbstractKeyListModel;
class AbstractKeyListSortFilterProxyModel;
class KeyListSortFilterProxyModel;
class SearchBar;
class KeyTreeView : public QWidget
{
Q_OBJECT
public:
explicit KeyTreeView(QWidget *parent = nullptr);
KeyTreeView(const QString &stringFilter,
const std::shared_ptr<KeyFilter> &keyFilter,
AbstractKeyListSortFilterProxyModel *additionalProxy,
QWidget *parent,
const KConfigGroup &group);
~KeyTreeView() override;
TreeView *view() const
{
return m_view;
}
AbstractKeyListModel *model() const
{
return m_isHierarchical ? hierarchicalModel() : flatModel();
}
AbstractKeyListModel *flatModel() const
{
return m_flatModel;
}
AbstractKeyListModel *hierarchicalModel() const
{
return m_hierarchicalModel;
}
void setFlatModel(AbstractKeyListModel *model);
void setHierarchicalModel(AbstractKeyListModel *model);
- // extraOrigins is a map of fingerprint to origin that will be considered if the origin in the key is OriginUnknown.
- void setKeys(const std::vector<GpgME::Key> &keys, const std::map<QString, GpgME::Key::Origin> &extraOrigins = {});
+ // extraOrigins contains additional origin information for the keys. It must be in the same order as the keys themselves.
+ // For this reason, setKeys will NOT perform any sorting and filtering if extraOrigins is not empty.
+ void setKeys(const std::vector<GpgME::Key> &keys, const std::vector<GpgME::Key::Origin> &extraOrigins = {});
const std::vector<GpgME::Key> &keys() const
{
return m_keys;
}
void selectKeys(const std::vector<GpgME::Key> &keys);
std::vector<GpgME::Key> selectedKeys() const;
void addKeysUnselected(const std::vector<GpgME::Key> &keys);
void addKeysSelected(const std::vector<GpgME::Key> &keys);
void removeKeys(const std::vector<GpgME::Key> &keys);
#if 0
void setToolTipOptions(int options);
int toolTipOptions() const;
#endif
QString stringFilter() const
{
return m_stringFilter;
}
const std::shared_ptr<KeyFilter> &keyFilter() const
{
return m_keyFilter;
}
bool isHierarchicalView() const
{
return m_isHierarchical;
}
void setColumnSizes(const std::vector<int> &sizes);
std::vector<int> columnSizes() const;
void setSortColumn(int sortColumn, Qt::SortOrder sortOrder);
int sortColumn() const;
Qt::SortOrder sortOrder() const;
virtual KeyTreeView *clone() const
{
return new KeyTreeView(*this);
}
void disconnectSearchBar();
bool connectSearchBar(const SearchBar *bar);
void resizeColumns();
void restoreLayout(const KConfigGroup &group);
public Q_SLOTS:
virtual void setStringFilter(const QString &text);
virtual void setKeyFilter(const std::shared_ptr<Kleo::KeyFilter> &filter);
virtual void setHierarchicalView(bool on);
Q_SIGNALS:
void stringFilterChanged(const QString &filter);
void keyFilterChanged(const std::shared_ptr<Kleo::KeyFilter> &filter);
void hierarchicalChanged(bool on);
protected:
KeyTreeView(const KeyTreeView &);
private:
void init();
void addKeysImpl(const std::vector<GpgME::Key> &, bool);
void restoreExpandState();
void setUpTagKeys();
void updateModelConnections(AbstractKeyListModel *oldModel, AbstractKeyListModel *newModel);
void saveStateBeforeModelChange();
void restoreStateAfterModelChange();
private:
std::vector<GpgME::Key> m_keys;
KeyListSortFilterProxyModel *m_proxy;
AbstractKeyListSortFilterProxyModel *m_additionalProxy;
TreeView *m_view;
AbstractKeyListModel *m_flatModel;
AbstractKeyListModel *m_hierarchicalModel;
QString m_stringFilter;
std::shared_ptr<KeyFilter> m_keyFilter;
QStringList m_expandedKeys;
std::vector<GpgME::Key> m_selectedKeys;
GpgME::Key m_currentKey;
std::vector<QMetaObject::Connection> m_connections;
KConfigGroup m_group;
bool m_isHierarchical : 1;
bool m_onceResized : 1;
};
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 18, 10:50 PM (1 d, 12 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e5/ef/41b4b7576f90d79e4fece49c1713

Event Timeline