Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp
index c07d32ef5..a8bc69a62 100644
--- a/src/commands/importcertificatescommand.cpp
+++ b/src/commands/importcertificatescommand.cpp
@@ -1,676 +1,677 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/importcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "importcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "certifycertificatecommand.h"
#include "kleopatra_debug.h"
+#include <Libkleo/KeyList>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QGpgME/ImportJob>
#include <QGpgME/ImportFromKeyserverJob>
#include <QGpgME/ChangeOwnerTrustJob>
#include <gpgme++/global.h>
#include <gpgme++/importresult.h>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QByteArray>
#include <QEventLoop>
#include <QString>
#include <QWidget>
#include <QTreeView>
#include <QTextDocument> // for Qt::escape
#include <memory>
#include <algorithm>
#include <map>
#include <set>
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
namespace
{
make_comparator_str(ByImportFingerprint, .fingerprint());
class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ImportResultProxyModel(const std::vector<ImportResult> &results, const QStringList &ids, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
updateFindCache(results, ids);
}
~ImportResultProxyModel() override {}
ImportResultProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ImportResultProxyModel(*this);
}
void setImportResults(const std::vector<ImportResult> &results, const QStringList &ids)
{
updateFindCache(results, ids);
invalidateFilter();
}
protected:
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid() || role != Qt::ToolTipRole) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
}
- const QString fpr = index.data(FingerprintRole).toString();
+ const QString fpr = index.data(KeyList::FingerprintRole).toString();
// find information:
const std::vector<Import>::const_iterator it
= qBinaryFind(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint<std::less>());
if (it == m_importsByFingerprint.end()) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
} else {
QStringList rv;
const auto ids = m_idsByFingerprint[it->fingerprint()];
rv.reserve(ids.size());
std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv));
return Formatting::importMetaData(*it, rv);
}
}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
//
// 0. Keep parents of matching children:
//
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
Q_ASSERT(index.isValid());
for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i)
if (filterAcceptsRow(i, index)) {
return true;
}
//
// 1. Check that this is an imported key:
//
- const QString fpr = index.data(FingerprintRole).toString();
+ const QString fpr = index.data(KeyList::FingerprintRole).toString();
return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint<std::less>());
}
private:
void updateFindCache(const std::vector<ImportResult> &results, const QStringList &ids)
{
Q_ASSERT(results.size() == static_cast<unsigned>(ids.size()));
m_importsByFingerprint.clear();
m_idsByFingerprint.clear();
m_results = results;
for (unsigned int i = 0, end = results.size(); i != end; ++i) {
const std::vector<Import> imports = results[i].imports();
m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end());
const QString &id = ids[i];
for (std::vector<Import>::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) {
m_idsByFingerprint[it->fingerprint()].insert(id);
}
}
std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
ByImportFingerprint<std::less>());
}
private:
mutable std::vector<Import> m_importsByFingerprint;
mutable std::map< const char *, std::set<QString>, ByImportFingerprint<std::less> > m_idsByFingerprint;
std::vector<ImportResult> m_results;
};
}
ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c),
waitForMoreJobs(false),
containedExternalCMSCerts(false),
nonWorkingProtocols(),
idsByJob(),
jobs(),
results(),
ids()
{
}
ImportCertificatesCommand::Private::~Private() {}
#define d d_func()
#define q q_func()
ImportCertificatesCommand::ImportCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ImportCertificatesCommand::~ImportCertificatesCommand() {}
static QString format_ids(const QStringList &ids)
{
QStringList escapedIds;
for (const QString &id : ids) {
if (!id.isEmpty()) {
escapedIds << id.toHtmlEscaped();
}
}
return escapedIds.join(QLatin1String("<br>"));
}
static QString make_tooltip(const QStringList &ids)
{
if (ids.empty()) {
return QString();
}
if (ids.size() == 1)
if (ids.front().isEmpty()) {
return QString();
} else
return i18nc("@info:tooltip",
"Imported Certificates from %1",
ids.front().toHtmlEscaped());
else
return i18nc("@info:tooltip",
"Imported certificates from these sources:<br/>%1",
format_ids(ids));
}
void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector<ImportResult> &results, const QStringList &ids)
{
if (std::none_of(results.cbegin(), results.cend(), std::mem_fn(&ImportResult::numConsidered))) {
return;
}
q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"),
new ImportResultProxyModel(results, ids),
make_tooltip(ids));
if (QTreeView *const tv = qobject_cast<QTreeView *>(parentWidgetOrView())) {
tv->expandAll();
}
}
int sum(const std::vector<ImportResult> &res, int (ImportResult::*fun)() const)
{
return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0);
}
static QString make_report(const std::vector<ImportResult> &res, const QString &id = QString())
{
const KLocalizedString normalLine = ki18n("<tr><td align=\"right\">%1</td><td>%2</td></tr>");
const KLocalizedString boldLine = ki18n("<tr><td align=\"right\"><b>%1</b></td><td>%2</td></tr>");
const KLocalizedString headerLine = ki18n("<tr><th colspan=\"2\" align=\"center\">%1</th></tr>");
#define SUM( x ) sum( res, &ImportResult::x )
QStringList lines;
if (!id.isEmpty()) {
lines.push_back(headerLine.subs(id).toString());
}
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(SUM(numConsidered)).toString());
lines.push_back(normalLine.subs(i18n("Imported:"))
.subs(SUM(numImported)).toString());
if (const int n = SUM(newSignatures))
lines.push_back(normalLine.subs(i18n("New signatures:"))
.subs(n).toString());
if (const int n = SUM(newUserIDs))
lines.push_back(normalLine.subs(i18n("New user IDs:"))
.subs(n).toString());
if (const int n = SUM(numKeysWithoutUserID))
lines.push_back(normalLine.subs(i18n("Certificates without user IDs:"))
.subs(n).toString());
if (const int n = SUM(newSubkeys))
lines.push_back(normalLine.subs(i18n("New subkeys:"))
.subs(n).toString());
if (const int n = SUM(newRevocations))
lines.push_back(boldLine.subs(i18n("Newly revoked:"))
.subs(n).toString());
if (const int n = SUM(notImported))
lines.push_back(boldLine.subs(i18n("Not imported:"))
.subs(n).toString());
if (const int n = SUM(numUnchanged))
lines.push_back(normalLine.subs(i18n("Unchanged:"))
.subs(n).toString());
if (const int n = SUM(numSecretKeysConsidered))
lines.push_back(normalLine.subs(i18n("Secret keys processed:"))
.subs(n).toString());
if (const int n = SUM(numSecretKeysImported))
lines.push_back(normalLine.subs(i18n("Secret keys imported:"))
.subs(n).toString());
if (const int n = SUM(numSecretKeysConsidered) - SUM(numSecretKeysImported) - SUM(numSecretKeysUnchanged))
if (n > 0)
lines.push_back(boldLine.subs(i18n("Secret keys <em>not</em> imported:"))
.subs(n).toString());
if (const int n = SUM(numSecretKeysUnchanged))
lines.push_back(normalLine.subs(i18n("Secret keys unchanged:"))
.subs(n).toString());
if (const int n = SUM(numV3KeysSkipped))
lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:"))
.subs(n).toString());
#undef sum
return lines.join(QString());
}
static QString make_message_report(const std::vector<ImportResult> &res, const QStringList &ids)
{
Q_ASSERT(res.size() == static_cast<unsigned>(ids.size()));
if (res.empty()) {
return i18n("No imports (should not happen, please report a bug).");
}
if (res.size() == 1)
return ids.front().isEmpty()
? i18n("<qt><p>Detailed results of certificate import:</p>"
"<table width=\"100%\">%1</table></qt>", make_report(res))
: i18n("<qt><p>Detailed results of importing %1:</p>"
"<table width=\"100%\">%2</table></qt>", ids.front(), make_report(res));
return i18n("<qt><p>Detailed results of certificate import:</p>"
"<table width=\"100%\">%1</table></qt>", make_report(res, i18n("Totals")));
}
// Returns false on error, true if please certify was shown.
bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp)
{
const char *fpr = imp.fingerprint();
if (!fpr) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Import without fingerprint";
return false;
}
// Exactly one public key imported. Let's see if it is openpgp. We are async here so
// we can just fetch it.
auto ctx = GpgME::Context::createForProtocol(GpgME::OpenPGP);
if (!ctx) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto";
return false;
}
GpgME::Error err;
auto key = ctx->key(fpr, err, false);
delete ctx;
if (key.isNull() || err) {
// No such key most likely not OpenPGP
return false;
}
for (const auto &uid: key.userIDs()) {
if (uid.validity() >= GpgME::UserID::Marginal) {
// Already marginal so don't bug the user
return false;
}
}
const QStringList suggestions = QStringList() << i18n("A phone call to the person.")
<< i18n("Using a business card.")
<< i18n("Confirming it on a trusted website.");
auto sel = KMessageBox::questionYesNo(parentWidgetOrView(),
i18n("In order to mark the certificate as valid (green) it needs to be certified.") + QStringLiteral("<br>") +
i18n("Certifying means that you check the Fingerprint.") + QStringLiteral("<br>") +
i18n("Some suggestions to do this are:") +
QStringLiteral("<li><ul>%1").arg(suggestions.join(QStringLiteral("</ul><ul>"))) +
QStringLiteral("</ul></li>") +
i18n("Do you wish to start this process now?"),
i18nc("@title", "You have imported a new certificate (public key)"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("CertifyQuestion"));
if (sel == KMessageBox::Yes) {
QEventLoop loop;
auto cmd = new Commands::CertifyCertificateCommand(key);
cmd->setParentWidget(parentWidgetOrView());
loop.connect(cmd, SIGNAL(finished()), SLOT(quit()));
QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
}
return true;
}
void ImportCertificatesCommand::Private::showDetails(QWidget *parent, const std::vector<ImportResult> &res, const QStringList &ids)
{
if (res.size() == 1 && res[0].numImported() == 1 && res[0].imports().size() == 1) {
if (showPleaseCertify(res[0].imports()[0])) {
return;
}
}
setImportResultProxyModel(res, ids);
KMessageBox::information(parent, make_message_report(res, ids), i18n("Certificate Import Result"));
}
void ImportCertificatesCommand::Private::showDetails(const std::vector<ImportResult> &res, const QStringList &ids)
{
showDetails(parentWidgetOrView(), res, ids);
}
static QString make_error_message(const Error &err, const QString &id)
{
Q_ASSERT(err);
Q_ASSERT(!err.isCanceled());
return id.isEmpty()
? i18n("<qt><p>An error occurred while trying "
"to import the certificate:</p>"
"<p><b>%1</b></p></qt>",
QString::fromLocal8Bit(err.asString()))
: i18n("<qt><p>An error occurred while trying "
"to import the certificate %1:</p>"
"<p><b>%2</b></p></qt>",
id, QString::fromLocal8Bit(err.asString()));
}
void ImportCertificatesCommand::Private::showError(QWidget *parent, const Error &err, const QString &id)
{
if (parent) {
KMessageBox::error(parent, make_error_message(err, id), i18n("Certificate Import Failed"));
} else {
showError(err, id);
}
}
void ImportCertificatesCommand::Private::showError(const Error &err, const QString &id)
{
error(make_error_message(err, id), i18n("Certificate Import Failed"));
}
void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait)
{
if (wait == waitForMoreJobs) {
return;
}
waitForMoreJobs = wait;
tryToFinish();
}
void ImportCertificatesCommand::Private::importResult(const ImportResult &result)
{
jobs.erase(std::remove(jobs.begin(), jobs.end(), q->sender()), jobs.end());
importResult(result, idsByJob[q->sender()]);
}
void ImportCertificatesCommand::Private::importResult(const ImportResult &result, const QString &id)
{
results.push_back(result);
ids.push_back(id);
tryToFinish();
}
static void handleOwnerTrust(const std::vector<GpgME::ImportResult> &results)
{
//iterate over all imported certificates
for (const ImportResult &result : results) {
//when a new certificate got a secret key
if (result.numSecretKeysImported() >= 1) {
const char *fingerPr = result.imports()[0].fingerprint();
GpgME::Error err;
QScopedPointer<Context>
ctx(Context::createForProtocol(GpgME::Protocol::OpenPGP));
if (!ctx){
qCWarning(KLEOPATRA_LOG) << "Failed to get context";
continue;
}
const Key toTrustOwner = ctx->key(fingerPr, err , false);
if (toTrustOwner.isNull()) {
return;
}
QStringList uids;
uids.reserve(toTrustOwner.userIDs().size());
Q_FOREACH (const UserID &uid, toTrustOwner.userIDs()) {
uids << Formatting::prettyNameAndEMail(uid);
}
const QString str = xi18nc("@info",
"<title>You have imported a Secret Key.</title>"
"<para>The key has the fingerprint:<nl/>"
"<numid>%1</numid>"
"</para>"
"<para>And claims the User IDs:"
"<list><item>%2</item></list>"
"</para>"
"Is this your own key? (Set trust level to ultimate)",
QString::fromUtf8(fingerPr),
uids.join(QLatin1String("</item><item>")));
int k = KMessageBox::questionYesNo(nullptr, str, i18nc("@title:window",
"Secret key imported"));
if (k == KMessageBox::Yes) {
//To use the ChangeOwnerTrustJob over
//the CryptoBackendFactory
const QGpgME::Protocol *const backend = QGpgME::openpgp();
if (!backend){
qCWarning(KLEOPATRA_LOG) << "Failed to get CryptoBackend";
return;
}
ChangeOwnerTrustJob *const j = backend->changeOwnerTrustJob();
j->start(toTrustOwner, Key::Ultimate);
}
}
}
}
void ImportCertificatesCommand::Private::handleExternalCMSImports()
{
QStringList fingerprints;
// For external CMS Imports we have to manually do a keylist
// with validation to get the intermediate and root ca imported
// automatically if trusted-certs and extra-certs are used.
for (const ImportResult &result : qAsConst(results)) {
const auto imports = result.imports();
for (const Import &import : imports) {
if (!import.fingerprint()) {
continue;
}
fingerprints << QString::fromLatin1(import.fingerprint());
}
}
auto job = QGpgME::smime()->keyListJob(false, true, true);
// Old connect here because of Windows.
connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector<GpgME::Key>,QString,GpgME::Error)),
q, SLOT(keyListDone(GpgME::KeyListResult,std::vector<GpgME::Key>,QString,GpgME::Error)));
job->start(fingerprints, false);
}
void ImportCertificatesCommand::Private::keyListDone(const GpgME::KeyListResult &,
const std::vector<GpgME::Key> &keys,
const QString &, const GpgME::Error&)
{
KeyCache::mutableInstance()->refresh(keys);
showDetails(results, ids);
auto tv = dynamic_cast<QTreeView *> (view());
if (!tv) {
qCDebug(KLEOPATRA_LOG) << "Failed to find treeview";
} else {
tv->expandAll();
}
finished();
}
void ImportCertificatesCommand::Private::tryToFinish()
{
if (waitForMoreJobs || !jobs.empty()) {
return;
}
if (std::any_of(results.cbegin(), results.cend(),
[](const GpgME::ImportResult &result) {
return result.error().code();
})) {
setImportResultProxyModel(results, ids);
if (std::all_of(results.cbegin(), results.cend(),
[](const GpgME::ImportResult &result) {
return result.error().isCanceled();
})) {
Q_EMIT q->canceled();
} else {
for (unsigned int i = 0, end = results.size(); i != end; ++i)
if (const Error err = results[i].error()) {
showError(err, ids[i]);
}
}
} else {
if (containedExternalCMSCerts) {
handleExternalCMSImports();
// We emit finished and do show details
// after the keylisting.
return;
} else {
handleOwnerTrust(results);
}
showDetails(results, ids);
}
finished();
}
static std::unique_ptr<ImportJob> get_import_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr<ImportJob>(backend->importJob());
} else {
return std::unique_ptr<ImportJob>();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr<ImportJob> job = get_import_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
importResult(ImportResult(), id);
return;
}
connect(job.get(), SIGNAL(result(GpgME::ImportResult)),
q, SLOT(importResult(GpgME::ImportResult)));
connect(job.get(), &Job::progress,
q, &Command::progress);
const GpgME::Error err = job->start(data);
if (err.code()) {
importResult(ImportResult(err), id);
} else {
jobs.push_back(job.release());
idsByJob[jobs.back()] = id;
}
}
static std::unique_ptr<ImportFromKeyserverJob> get_import_from_keyserver_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr<ImportFromKeyserverJob>(backend->importFromKeyserverJob());
} else {
return std::unique_ptr<ImportFromKeyserverJob>();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector<Key> &keys, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr<ImportFromKeyserverJob> job = get_import_from_keyserver_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
importResult(ImportResult(), id);
return;
}
if (protocol == GpgME::CMS) {
containedExternalCMSCerts = true;
}
connect(job.get(), SIGNAL(result(GpgME::ImportResult)),
q, SLOT(importResult(GpgME::ImportResult)));
connect(job.get(), &Job::progress,
q, &Command::progress);
const GpgME::Error err = job->start(keys);
if (err.code()) {
importResult(ImportResult(err), id);
} else {
jobs.push_back(job.release());
idsByJob[jobs.back()] = id;
}
}
void ImportCertificatesCommand::doCancel()
{
std::for_each(d->jobs.begin(), d->jobs.end(), [](Job *job) { job->slotCancel(); });
}
#undef d
#undef q
#include "moc_importcertificatescommand.cpp"
#include "importcertificatescommand.moc"
diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp
index 2eccd2c37..218556c60 100644
--- a/src/crypto/gui/certificatelineedit.cpp
+++ b/src/crypto/gui/certificatelineedit.cpp
@@ -1,259 +1,260 @@
/* crypto/gui/certificatelineedit.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "certificatelineedit.h"
#include <QCompleter>
#include <QPushButton>
#include <QAction>
#include <QSignalBlocker>
#include "kleopatra_debug.h"
#include "commands/detailscommand.h"
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
+#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <KIconLoader>
#include <gpgme++/key.h>
#include <QGpgME/KeyForMailboxJob>
#include <QGpgME/Protocol>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
static QStringList s_lookedUpKeys;
namespace
{
class ProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject *parent = nullptr)
: KeyListSortFilterProxyModel(parent)
{
}
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DecorationRole: {
- const auto key = KeyListSortFilterProxyModel::data(index,
- Kleo::KeyListModelInterface::KeyRole).value<GpgME::Key>();
+ const auto key = KeyListSortFilterProxyModel::data(index, KeyList::KeyRole).value<GpgME::Key>();
Q_ASSERT(!key.isNull());
if (key.isNull()) {
return QVariant();
}
return Kleo::Formatting::iconForUid(key.userID(0));
}
default:
return KeyListSortFilterProxyModel::data(index, role);
}
}
};
} // namespace
CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model,
QWidget *parent,
KeyFilter *filter)
: QLineEdit(parent),
mFilterModel(new KeyListSortFilterProxyModel(this)),
mCompleterFilterModel(new ProxyModel(this)),
mFilter(std::shared_ptr<KeyFilter>(filter)),
mLineAction(new QAction(this))
{
setPlaceholderText(i18n("Please enter a name or email address..."));
setClearButtonEnabled(true);
addAction(mLineAction, QLineEdit::LeadingPosition);
mCompleterFilterModel->setKeyFilter(mFilter);
mCompleterFilterModel->setSourceModel(model);
auto *completer = new QCompleter(this);
completer->setModel(mCompleterFilterModel);
- completer->setCompletionColumn(KeyListModelInterface::Summary);
+ completer->setCompletionColumn(KeyList::Summary);
completer->setFilterMode(Qt::MatchContains);
completer->setCaseSensitivity(Qt::CaseInsensitive);
setCompleter(completer);
mFilterModel->setSourceModel(model);
- mFilterModel->setFilterKeyColumn(KeyListModelInterface::Summary);
+ mFilterModel->setFilterKeyColumn(KeyList::Summary);
if (filter) {
mFilterModel->setKeyFilter(mFilter);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keyListingDone,
this, &CertificateLineEdit::updateKey);
connect(this, &QLineEdit::editingFinished,
this, &CertificateLineEdit::editFinished);
connect(this, &QLineEdit::textChanged,
this, &CertificateLineEdit::editChanged);
connect(mLineAction, &QAction::triggered,
this, &CertificateLineEdit::dialogRequested);
connect(this, &QLineEdit::editingFinished, this,
&CertificateLineEdit::checkLocate);
updateKey();
/* Take ownership of the model to prevent double deletion when the
* filter models are deleted */
model->setParent(parent ? parent : this);
}
void CertificateLineEdit::editChanged()
{
updateKey();
if (!mEditStarted) {
Q_EMIT editingStarted();
mEditStarted = true;
}
mEditFinished = false;
}
void CertificateLineEdit::editFinished()
{
mEditStarted = false;
mEditFinished = true;
updateKey();
}
void CertificateLineEdit::checkLocate()
{
if (!key().isNull()) {
// Already have a key
return;
}
// Only check once per mailbox
const auto mailText = text();
if (s_lookedUpKeys.contains(mailText)) {
return;
}
s_lookedUpKeys << mailText;
qCDebug(KLEOPATRA_LOG) << "Lookup job for" << mailText;
QGpgME::KeyForMailboxJob *job = QGpgME::openpgp()->keyForMailboxJob();
job->start(mailText);
}
void CertificateLineEdit::updateKey()
{
const auto mailText = text();
auto newKey = Key();
if (mailText.isEmpty()) {
mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
mLineAction->setToolTip(i18n("Open selection dialog."));
} else {
mFilterModel->setFilterFixedString(mailText);
if (mFilterModel->rowCount() > 1) {
if (mEditFinished) {
mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("question")).pixmap(KIconLoader::SizeSmallMedium));
mLineAction->setToolTip(i18n("Multiple certificates"));
} else {
mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
mLineAction->setToolTip(i18n("Open selection dialog."));
}
} else if (mFilterModel->rowCount() == 1) {
- newKey = mFilterModel->data(mFilterModel->index(0, 0), KeyListModelInterface::KeyRole).value<Key>();
+ const auto index = mFilterModel->index(0, 0);
+ newKey = mFilterModel->data(index, KeyList::KeyRole).value<Key>();
mLineAction->setToolTip(Formatting::validity(newKey.userID(0)) +
QStringLiteral("<br/>Click here for details."));
/* FIXME: This needs to be solved by a multiple UID supporting model */
mLineAction->setIcon(Formatting::iconForUid(newKey.userID(0)));
} else {
mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")));
mLineAction->setToolTip(i18n("No matching certificates found.<br/>Click here to import a certificate."));
}
}
mKey = newKey;
if (mKey.isNull()) {
setToolTip(QString());
} else {
setToolTip(Formatting::toolTip(newKey, Formatting::ToolTipOption::AllOptions));
}
Q_EMIT keyChanged();
if (mailText.isEmpty()) {
Q_EMIT wantsRemoval(this);
}
}
Key CertificateLineEdit::key() const
{
if (isEnabled()) {
return mKey;
} else {
return Key();
}
}
void CertificateLineEdit::dialogRequested()
{
if (!mKey.isNull()) {
auto cmd = new Commands::DetailsCommand(mKey, nullptr);
cmd->start();
return;
}
CertificateSelectionDialog *const dlg = new CertificateSelectionDialog(this);
dlg->setKeyFilter(mFilter);
if (dlg->exec()) {
const std::vector<Key> keys = dlg->selectedCertificates();
if (!keys.size()) {
return;
}
for (unsigned int i = 0; i < keys.size(); i++) {
if (!i) {
setKey(keys[i]);
} else {
Q_EMIT addRequested(keys[i]);
}
}
}
delete dlg;
updateKey();
}
void CertificateLineEdit::setKey(const Key &k)
{
QSignalBlocker blocky(this);
qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(k);
setText(Formatting::summaryLine(k));
updateKey();
}
bool CertificateLineEdit::isEmpty() const
{
return text().isEmpty();
}
void CertificateLineEdit::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
mFilter = filter;
mFilterModel->setKeyFilter(filter);
mCompleterFilterModel->setKeyFilter(mFilter);
updateKey();
}
#include "certificatelineedit.moc"
diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp
index abc2c1643..d998d58e1 100644
--- a/src/view/keytreeview.cpp
+++ b/src/view/keytreeview.cpp
@@ -1,697 +1,698 @@
/* -*- 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 <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeyRearrangeColumnsProxyModel>
#include <Libkleo/Predicates>
#include "utils/headerview.h"
#include "utils/tags.h"
#include <Libkleo/Stl_Util>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyCache>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
#include <QTimer>
#include <QTreeView>
#include <QHeaderView>
#include <QItemSelectionModel>
#include <QItemSelection>
#include <QLayout>
#include <QList>
#include <QMenu>
#include <QAction>
#include <QEvent>
#include <QContextMenuEvent>
#include <KSharedConfig>
#include <KLocalizedString>
#include <gpgme++/gpgmepp_version.h>
#if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
# define GPGME_HAS_REMARKS
#endif
#define TAGS_COLUMN 13
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
class TreeView : public QTreeView
{
public:
explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent)
{
header()->installEventFilter(this);
}
QSize minimumSizeHint() const override
{
const QSize min = QTreeView::minimumSizeHint();
return QSize(min.width(), min.height() + 5 * fontMetrics().height());
}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched)
if (event->type() == QEvent::ContextMenu) {
QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event);
if (!mHeaderPopup) {
mHeaderPopup = new QMenu(this);
mHeaderPopup->setTitle(i18n("View Columns"));
for (int i = 0; i < model()->columnCount(); ++i) {
QAction *tmp
= mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString());
tmp->setData(QVariant(i));
tmp->setCheckable(true);
mColumnActions << tmp;
}
connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) {
const int col = action->data().toInt();
if ((col == TAGS_COLUMN) && action->isChecked()) {
Tags::enableTags();
}
if (action->isChecked()) {
showColumn(col);
} else {
hideColumn(col);
}
KeyTreeView *tv = qobject_cast<KeyTreeView *> (parent());
if (tv) {
tv->resizeColumns();
}
});
}
for (QAction *action : qAsConst(mColumnActions)) {
const int column = action->data().toInt();
action->setChecked(!isColumnHidden(column));
}
mHeaderPopup->popup(mapToGlobal(e->pos()));
return true;
}
return false;
}
private:
QMenu *mHeaderPopup = nullptr;
QList<QAction *> mColumnActions;
};
} // anon namespace
KeyTreeView::KeyTreeView(QWidget *parent)
: QWidget(parent),
m_proxy(new KeyListSortFilterProxyModel(this)),
m_additionalProxy(nullptr),
m_view(new TreeView(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 TreeView(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 TreeView(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 (HeaderView *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 (HeaderView *const hv = static_cast<HeaderView *>(m_view->header())) {
return hv->sectionSizes();
} else {
return std::vector<int>();
}
}
void KeyTreeView::init()
{
KDAB_SET_OBJECT_NAME(m_proxy);
KDAB_SET_OBJECT_NAME(m_view);
if (!m_group.isValid()) {
m_group = KSharedConfig::openConfig()->group("KeyTreeView_default");
} else {
// 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);
HeaderView *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->setAlternatingRowColors( true );
m_view->setAllColumnsShowFocus(true);
m_view->setSortingEnabled(true);
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->setFilterFixedString(m_stringFilter);
m_proxy->setKeyFilter(m_keyFilter);
m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
KeyRearrangeColumnsProxyModel *rearangingModel = new KeyRearrangeColumnsProxyModel(this);
rearangingModel->setSourceModel(m_proxy);
- rearangingModel->setSourceColumns(QVector<int>() << KeyListModelInterface::PrettyName
- << KeyListModelInterface::PrettyEMail
- << KeyListModelInterface::Validity
- << KeyListModelInterface::ValidFrom
- << KeyListModelInterface::ValidUntil
- << KeyListModelInterface::TechnicalDetails
- << KeyListModelInterface::KeyID
- << KeyListModelInterface::Fingerprint
- << KeyListModelInterface::OwnerTrust
- << KeyListModelInterface::Origin
- << KeyListModelInterface::LastUpdate
- << KeyListModelInterface::Issuer
- << KeyListModelInterface::SerialNumber
+ rearangingModel->setSourceColumns(QVector<int>() << 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
#ifdef GPGME_HAS_REMARKS
// If a column is added before this TAGS_COLUMN define has to be updated accordingly
- << KeyListModelInterface::Remarks
+ << KeyList::Remarks
#endif
);
m_view->setModel(rearangingModel);
/* Handle expansion state */
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(Kleo::KeyListModelInterface::KeyRole).value<GpgME::Key>();
+ const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
const auto fpr = QString::fromLatin1(key.primaryFingerprint());
if (m_expandedKeys.contains(fpr)) {
return;
}
m_expandedKeys << fpr;
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(Kleo::KeyListModelInterface::KeyRole).value<GpgME::Key>();
+ const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint()));
m_group.writeEntry("Expanded", m_expandedKeys);
});
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () {
/* We use a single shot timer here to ensure that the keysMayHaveChanged
* handlers are all handled before we restore the expand state so that
* the model is already populated. */
QTimer::singleShot(0, [this] () {
restoreExpandState();
setUpTagKeys();
if (!m_onceResized) {
m_onceResized = true;
resizeColumns();
}
});
});
resizeColumns();
restoreLayout();
}
void KeyTreeView::restoreExpandState()
{
if (!KeyCache::instance()->initialized()) {
qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting.";
return;
}
for (const auto &fpr: qAsConst(m_expandedKeys)) {
const KeyListModelInterface *km = dynamic_cast<const KeyListModelInterface*> (m_view->model());
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()
{
#ifdef GPGME_HAS_REMARKS
const auto tagKeys = Tags::tagKeys();
if (m_hierarchicalModel) {
m_hierarchicalModel->setRemarkKeys(tagKeys);
}
if (m_flatModel) {
m_flatModel->setRemarkKeys(tagKeys);
}
#endif
}
void KeyTreeView::saveLayout()
{
QHeaderView *header = m_view->header();
QVariantList columnVisibility;
QVariantList columnOrder;
QVariantList columnWidths;
const int headerCount = header->count();
columnVisibility.reserve(headerCount);
columnWidths.reserve(headerCount);
columnOrder.reserve(headerCount);
for (int i = 0; i < headerCount; ++i) {
columnVisibility << QVariant(!m_view->isColumnHidden(i));
columnWidths << QVariant(header->sectionSize(i));
columnOrder << QVariant(header->visualIndex(i));
}
m_group.writeEntry("ColumnVisibility", columnVisibility);
m_group.writeEntry("ColumnOrder", columnOrder);
m_group.writeEntry("ColumnWidths", columnWidths);
m_group.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
if (header->isSortIndicatorShown()) {
m_group.writeEntry("SortColumn", header->sortIndicatorSection());
} else {
m_group.writeEntry("SortColumn", -1);
}
}
void KeyTreeView::restoreLayout()
{
QHeaderView *header = m_view->header();
QVariantList columnVisibility = m_group.readEntry("ColumnVisibility", QVariantList());
QVariantList columnOrder = m_group.readEntry("ColumnOrder", QVariantList());
QVariantList columnWidths = m_group.readEntry("ColumnWidths", QVariantList());
if (columnVisibility.isEmpty()) {
// 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 {
for (int i = 0; i < header->count(); ++i) {
if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) {
// An additional column that was not around last time we saved.
// We default to hidden.
m_view->hideColumn(i);
continue;
}
bool visible = columnVisibility[i].toBool();
int width = columnWidths[i].toInt();
int order = columnOrder[i].toInt();
header->resizeSection(i, width ? width : 100);
header->moveSection(header->visualIndex(i), order);
if ((i == TAGS_COLUMN) && visible) {
Tags::enableTags();
}
if (!visible) {
m_view->hideColumn(i);
}
}
m_onceResized = true;
}
int sortOrder = m_group.readEntry("SortAscending", (int)Qt::AscendingOrder);
int sortColumn = m_group.readEntry("SortColumn", -1);
if (sortColumn >= 0) {
m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
}
}
KeyTreeView::~KeyTreeView()
{
saveLayout();
}
static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm)
{
Q_ASSERT(pm);
while (QAbstractProxyModel *const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) {
pm = sm;
}
return pm;
}
void KeyTreeView::setFlatModel(AbstractKeyListModel *model)
{
if (model == m_flatModel) {
return;
}
m_flatModel = model;
if (!m_isHierarchical)
// TODO: this fails when called after setHierarchicalView( false )...
{
find_last_proxy(m_proxy)->setSourceModel(model);
}
}
void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == m_hierarchicalModel) {
return;
}
m_hierarchicalModel = model;
if (m_isHierarchical) {
find_last_proxy(m_proxy)->setSourceModel(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->setFilterFixedString(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);
}
static QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const KeyListSortFilterProxyModel &proxy)
{
QItemSelection result;
for (const Key &key : keys) {
const QModelIndex mi = proxy.index(key);
if (mi.isValid()) {
result.merge(QItemSelection(mi, mi), QItemSelectionModel::Select);
}
}
return result;
}
void KeyTreeView::selectKeys(const std::vector<Key> &keys)
{
m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_proxy), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
std::vector<Key> KeyTreeView::selectedKeys() const
{
return m_proxy->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 = m_proxy->keys(m_view->selectionModel()->selectedRows());
const Key currentKey = m_proxy->key(m_view->currentIndex());
m_isHierarchical = on;
find_last_proxy(m_proxy)->setSourceModel(model());
if (on) {
m_view->expandAll();
}
selectKeys(selectedKeys);
if (!currentKey.isNull()) {
const QModelIndex currentIndex = m_proxy->index(currentKey);
if (currentIndex.isValid()) {
m_view->selectionModel()->setCurrentIndex(m_proxy->index(currentKey), QItemSelectionModel::NoUpdate);
m_view->scrollTo(currentIndex);
}
}
Q_EMIT hierarchicalChanged(on);
}
void KeyTreeView::setKeys(const std::vector<Key> &keys)
{
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
m_keys = sorted;
if (m_flatModel) {
m_flatModel->setKeys(sorted);
}
if (m_hierarchicalModel) {
m_hierarchicalModel->setKeys(sorted);
}
}
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); });
}
}
static const struct {
const char *signal;
const char *slot;
} connections[] = {
{
SIGNAL(stringFilterChanged(QString)),
SLOT(setStringFilter(QString))
},
{
SIGNAL(keyFilterChanged(std::shared_ptr<Kleo::KeyFilter>)),
SLOT(setKeyFilter(std::shared_ptr<Kleo::KeyFilter>))
},
};
static const unsigned int numConnections = sizeof connections / sizeof * connections;
void KeyTreeView::disconnectSearchBar(const QObject *bar)
{
for (unsigned int i = 0; i < numConnections; ++i) {
disconnect(this, connections[i].signal, bar, connections[i].slot);
disconnect(bar, connections[i].signal, this, connections[i].slot);
}
}
bool KeyTreeView::connectSearchBar(const QObject *bar)
{
for (unsigned int i = 0; i < numConnections; ++i)
if (!connect(this, connections[i].signal, bar, connections[i].slot) ||
!connect(bar, connections[i].signal, this, connections[i].slot)) {
return false;
}
return true;
}
void KeyTreeView::resizeColumns()
{
- m_view->setColumnWidth(KeyListModelInterface::PrettyName, 260);
- m_view->setColumnWidth(KeyListModelInterface::PrettyEMail, 260);
+ 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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 7:04 PM (22 h, 13 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
87/31/c9df5a8b7644190eccbc763b9ac2

Event Timeline