Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34606572
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
64 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment