Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/command.cpp b/src/commands/command.cpp
index e767f8872..af60f90d0 100644
--- a/src/commands/command.cpp
+++ b/src/commands/command.cpp
@@ -1,323 +1,315 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "command.h"
#include "command_p.h"
#include "checksumverifyfilescommand.h"
#include "decryptverifyfilescommand.h"
#include "detailscommand.h"
#include "importcertificatefromfilecommand.h"
#include "lookupcertificatescommand.h"
#include "signencryptfilescommand.h"
#include "viewemailfilescommand.h"
#include <Libkleo/Classify>
#include <Libkleo/KeyCache>
#include <view/tabwidget.h>
#include "kleopatra_debug.h"
#include <KWindowSystem>
#include <QAbstractItemView>
#include <QFileInfo>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
Command::Private::Private(Command *qq)
: q(qq)
, autoDelete(true)
, warnWhenRunningAtShutdown(true)
{
}
Command::Private::Private(Command *qq, KeyListController *controller)
: q(qq)
, autoDelete(true)
, warnWhenRunningAtShutdown(true)
, controller_(controller)
{
}
Command::Private::Private(Command *qq, QWidget *parent)
: q(qq)
, autoDelete(true)
, warnWhenRunningAtShutdown(true)
, parentWidget_(parent)
{
}
Command::Private::~Private()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
}
Command::Command(KeyListController *p)
: QObject(p)
, d(new Private(this, p))
{
if (p) {
p->registerCommand(this);
}
}
Command::Command(QAbstractItemView *v, KeyListController *p)
: QObject(p)
, d(new Private(this, p))
{
if (p) {
p->registerCommand(this);
}
if (v) {
setView(v);
}
}
Command::Command(Private *pp)
: QObject(pp->controller_)
, d(pp)
{
if (pp->controller_) {
pp->controller_->registerCommand(this);
}
}
Command::Command(QAbstractItemView *v, Private *pp)
: QObject(pp->controller_)
, d(pp)
{
if (pp->controller_) {
pp->controller_->registerCommand(this);
}
if (v) {
setView(v);
}
}
Command::Command(const GpgME::Key &key)
: QObject(nullptr)
, d(new Private(this))
{
d->keys_ = std::vector<Key>(1, key);
}
Command::Command(const std::vector<GpgME::Key> &keys)
: QObject(nullptr)
, d(new Private(this))
{
d->keys_ = keys;
}
Command::Command(const Key &key, Private *pp)
: QObject(nullptr)
, d(pp)
{
d->keys_ = std::vector<Key>(1, key);
}
Command::Command(const std::vector<GpgME::Key> &keys, Private *pp)
: QObject(nullptr)
, d(pp)
{
d->keys_ = keys;
}
Command::~Command()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
}
void Command::setAutoDelete(bool on)
{
d->autoDelete = on;
}
bool Command::autoDelete() const
{
return d->autoDelete;
}
void Command::setWarnWhenRunningAtShutdown(bool on)
{
d->warnWhenRunningAtShutdown = on;
}
bool Command::warnWhenRunningAtShutdown() const
{
return d->warnWhenRunningAtShutdown;
}
void Command::setParentWidget(QWidget *widget)
{
d->parentWidget_ = widget;
}
void Command::setParentWId(WId wid)
{
d->parentWId_ = wid;
}
void Command::setView(QAbstractItemView *view)
{
if (view == d->view_) {
return;
}
d->view_ = view;
if (!view || !d->keys_.empty()) {
return;
}
const auto *const keyListModel = dynamic_cast<KeyListModelInterface *>(view->model());
if (!keyListModel) {
qCWarning(KLEOPATRA_LOG) << "view" << view << "has not key list model";
return;
}
const QItemSelectionModel *const sm = view->selectionModel();
if (!sm) {
qCWarning(KLEOPATRA_LOG) << "view" << view << "has no selection model";
return;
}
const QList<QModelIndex> selected = sm->selectedRows();
std::transform(selected.begin(), selected.end(), std::back_inserter(d->keys_), [keyListModel](const auto &idx) {
return keyListModel->key(idx);
});
}
void Command::setKey(const Key &key)
{
d->keys_.clear();
if (!key.isNull()) {
d->keys_.push_back(key);
}
}
void Command::setKeys(const std::vector<Key> &keys)
{
d->keys_ = keys;
}
void Command::start()
{
// defer the actual start and return immediately to avoid problems if the
// caller is deleted before start returns (e.g. an action of a context menu)
QMetaObject::invokeMethod(
this,
[this]() {
doStart();
},
Qt::QueuedConnection);
}
void Command::cancel()
{
qCDebug(KLEOPATRA_LOG) << metaObject()->className();
doCancel();
Q_EMIT canceled(QPrivateSignal{});
}
-void Command::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip)
-{
- if (TabWidget *const tw = d->controller_ ? d->controller_->tabWidget() : nullptr)
- if (QAbstractItemView *const v = tw->addTemporaryView(title, proxy, tabToolTip)) {
- setView(v);
- }
-}
-
void Command::applyWindowID(QWidget *w) const
{
if (w) {
if (d->parentWId()) {
if (QWidget *pw = QWidget::find(d->parentWId())) {
// remember the current focus widget; re-parenting resets it
QWidget *focusWidget = w->focusWidget();
w->setParent(pw, w->windowFlags());
if (focusWidget) {
focusWidget->setFocus();
}
} else {
w->setAttribute(Qt::WA_NativeWindow, true);
KWindowSystem::setMainWindow(w->windowHandle(), d->parentWId());
}
} else {
// remember the current focus widget; re-parenting resets it
QWidget *focusWidget = w->focusWidget();
w->setParent(d->parentWidgetOrView(), w->windowFlags());
if (focusWidget) {
focusWidget->setFocus();
}
}
}
}
// static
QList<Command *> Command::commandsForFiles(const QStringList &files, KeyListController *controller)
{
QStringList importFiles, decryptFiles, encryptFiles, checksumFiles, emailFiles;
QList<Command *> cmds;
for (const QString &fileName : files) {
const unsigned int classification = classify(fileName);
if (classification & Class::MimeFile) {
emailFiles << fileName;
} else if (classification & Class::AnyCertStoreType) {
importFiles << fileName;
} else if (classification & Class::AnyMessageType) {
// For any message we decrypt / verify. This includes
// the class CipherText
decryptFiles << fileName;
} else if (isChecksumFile(fileName)) {
checksumFiles << fileName;
} else {
QFileInfo fi(fileName);
if (fi.isReadable()) {
encryptFiles << fileName;
}
}
}
if (!importFiles.isEmpty()) {
cmds << new ImportCertificateFromFileCommand(importFiles, controller);
}
if (!decryptFiles.isEmpty()) {
cmds << new DecryptVerifyFilesCommand(decryptFiles, controller);
}
if (!encryptFiles.isEmpty()) {
cmds << new SignEncryptFilesCommand(encryptFiles, controller);
}
if (!checksumFiles.isEmpty()) {
cmds << new ChecksumVerifyFilesCommand(checksumFiles, controller);
}
if (!emailFiles.isEmpty()) {
cmds << new ViewEmailFilesCommand(emailFiles, controller);
}
return cmds;
}
// static
Command *Command::commandForQuery(const QString &query)
{
const auto cache = Kleo::KeyCache::instance();
GpgME::Key key = cache->findByKeyIDOrFingerprint(query.toLocal8Bit().data());
if (key.isNull() && Kleo::isFingerprint(query)) {
// Try to find the key by subkey fingerprint
const auto subkey = cache->findSubkeyByFingerprint(query.toStdString());
if (!subkey.isNull()) {
key = subkey.parent();
}
}
if (key.isNull()) {
return new LookupCertificatesCommand(query, nullptr);
} else {
return new DetailsCommand(key);
}
}
#include "moc_command.cpp"
diff --git a/src/commands/command.h b/src/commands/command.h
index b35850ea6..89b088e53 100644
--- a/src/commands/command.h
+++ b/src/commands/command.h
@@ -1,142 +1,139 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include <qwindowdefs.h> // for WId
#include <utils/types.h> // for ExecutionContext
#include <memory>
#include <vector>
class QModelIndex;
template<typename T>
class QList;
class QAbstractItemView;
namespace GpgME
{
class Key;
}
namespace Kleo
{
class KeyListController;
class AbstractKeyListSortFilterProxyModel;
class Command : public QObject, public ExecutionContext
{
Q_OBJECT
public:
explicit Command(KeyListController *parent);
explicit Command(QAbstractItemView *view, KeyListController *parent);
explicit Command(const GpgME::Key &key);
explicit Command(const std::vector<GpgME::Key> &keys);
~Command() override;
enum Restriction {
// clang-format off
NoRestriction = 0x0000,
NeedSelection = 0x0001,
OnlyOneKey = 0x0002,
NeedSecretKey = 0x0004, //< command performs secret key operations
NeedSecretPrimaryKeyData = 0x0008, //< command needs access to the secret key data of the primary key
NeedSecretSubkeyData = 0x0010, //< command needs access to the secret key data of one or more subkeys
// esoteric:
MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate = 0x0040, // for set-owner-trust
AnyCardHasNullPin = 0x0080,
SuitableForCard = 0x0100,
MustBeRoot = 0x0200,
MustBeTrustedRoot = 0x0400 | MustBeRoot,
MustBeUntrustedRoot = 0x0800 | MustBeRoot,
MustBeValid = 0x1000, //< key is neither revoked nor expired nor otherwise "bad"
MustBeOpenPGP = 0x2000,
MustBeCMS = 0x4000,
_AllRestrictions_Helper,
AllRestrictions = 2 * (_AllRestrictions_Helper - 1) - 1,
// clang-format on
};
Q_DECLARE_FLAGS(Restrictions, Restriction)
static Restrictions restrictions()
{
return NoRestriction;
}
/** Classify the files and return the most appropriate commands.
*
* @param files: A list of files.
*
* @returns null QString on success. Error message otherwise.
*/
static QList<Command *> commandsForFiles(const QStringList &files, KeyListController *controller);
/** Get a command for a query.
*
* @param query: A keyid / fingerprint or any string to use in the search.
*/
static Command *commandForQuery(const QString &query);
void setParentWidget(QWidget *widget);
// Prefer Command::setParentWidget over Command::setParentWId; QWidget::winId can cause unexpected problems
// when called on non-toplevel widgets
void setParentWId(WId wid);
void setView(QAbstractItemView *view);
void setKey(const GpgME::Key &key);
void setKeys(const std::vector<GpgME::Key> &keys);
void setAutoDelete(bool on);
bool autoDelete() const;
void setWarnWhenRunningAtShutdown(bool warn);
bool warnWhenRunningAtShutdown() const;
public Q_SLOTS:
void start();
void cancel();
Q_SIGNALS:
void info(const QString &message, int timeout = 0);
void progress(int current, int total);
void finished(QPrivateSignal);
void canceled(QPrivateSignal);
private:
virtual void doStart() = 0;
virtual void doCancel() = 0;
private:
void applyWindowID(QWidget *wid) const override;
-protected:
- void addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &tabToolTip = QString());
-
protected:
class Private;
const std::unique_ptr<Private> d;
protected:
explicit Command(Private *pp);
explicit Command(QAbstractItemView *view, Private *pp);
explicit Command(const std::vector<GpgME::Key> &keys, Private *pp);
explicit Command(const GpgME::Key &key, Private *pp);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Command::Restrictions)
}
diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp
index 333dbdaef..5f6c4c474 100644
--- a/src/commands/importcertificatescommand.cpp
+++ b/src/commands/importcertificatescommand.cpp
@@ -1,1108 +1,1118 @@
/*
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-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "importcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "certifycertificatecommand.h"
+#include "commands/detailscommand.h"
#include "kleopatra_debug.h"
+#include "view/keytreeview.h"
#include <settings.h>
#include <utils/memory-helpers.h>
#include <Libkleo/Algorithm>
+#include <Libkleo/AuditLogViewer>
#include <Libkleo/Compat>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyGroupImportExport>
#include <Libkleo/KeyHelpers>
#include <Libkleo/KeyList>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/MessageBox>
#include <Libkleo/Predicates>
#include <Libkleo/Stl_Util>
#include <QGpgME/ChangeOwnerTrustJob>
#include <QGpgME/ImportFromKeyserverJob>
#include <QGpgME/ImportJob>
#include <QGpgME/Protocol>
#include <QGpgME/ReceiveKeysJob>
#include <gpgme++/context.h>
#include <gpgme++/global.h>
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <KLocalizedString>
#include <KMessageBox>
+#include <KMessageDialog>
#include <QByteArray>
#include <QEventLoop>
+#include <QLabel>
#include <QProgressDialog>
+#include <QPushButton>
#include <QString>
#include <QTreeView>
+#include <QVBoxLayout>
#include <QWidget>
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <unordered_set>
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
static void disconnectConnection(const QMetaObject::Connection &connection)
{
// trivial function for disconnecting a signal-slot connection
QObject::disconnect(connection);
}
bool operator==(const ImportJobData &lhs, const ImportJobData &rhs)
{
return lhs.job == rhs.job;
}
namespace
{
make_comparator_str(ByImportFingerprint, .fingerprint());
class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ImportResultProxyModel(const std::vector<ImportResultData> &results, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
updateFindCache(results);
}
~ImportResultProxyModel() override
{
}
ImportResultProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ImportResultProxyModel(*this);
}
void setImportResults(const std::vector<ImportResultData> &results)
{
updateFindCache(results);
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(KeyList::FingerprintRole).toString();
// find information:
const std::vector<Import>::const_iterator it =
Kleo::binary_find(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(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<ImportResultData> &results)
{
m_importsByFingerprint.clear();
m_idsByFingerprint.clear();
m_results = results;
for (const auto &r : results) {
const std::vector<Import> imports = r.result.imports();
m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end());
for (std::vector<Import>::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) {
m_idsByFingerprint[it->fingerprint()].insert(r.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<ImportResultData> m_results;
};
bool importFailed(const ImportResultData &r)
{
// ignore GPG_ERR_EOF error to handle the "failed" import of files
// without X.509 certificates by gpgsm gracefully
return r.result.error() && r.result.error().code() != GPG_ERR_EOF;
}
bool importWasCanceled(const ImportResultData &r)
{
return r.result.error().isCanceled();
}
}
ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, certificateListWasEmpty(KeyCache::instance()->keys().empty())
, progressWindowTitle{i18nc("@title:window", "Importing Certificates")}
, progressLabelText{i18n("Importing certificates... (this can take a while)")}
{
}
ImportCertificatesCommand::Private::~Private()
{
if (progressDialog) {
delete progressDialog;
}
}
#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() = default;
-static QString format_ids(const std::vector<QString> &ids)
-{
- QStringList escapedIds;
- for (const QString &id : ids) {
- if (!id.isEmpty()) {
- escapedIds << id.toHtmlEscaped();
- }
- }
- return escapedIds.join(QLatin1StringView("<br>"));
-}
-
-static QString make_tooltip(const std::vector<ImportResultData> &results)
-{
- if (results.empty()) {
- return {};
- }
-
- std::vector<QString> ids;
- ids.reserve(results.size());
- std::transform(std::begin(results), std::end(results), std::back_inserter(ids), [](const auto &r) {
- return r.id;
- });
- std::sort(std::begin(ids), std::end(ids));
- ids.erase(std::unique(std::begin(ids), std::end(ids)), std::end(ids));
-
- if (ids.size() == 1)
- if (ids.front().isEmpty()) {
- return {};
- } 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<ImportResultData> &results)
-{
- if (std::none_of(std::begin(results), std::end(results), [](const auto &r) {
- return r.result.numConsidered() > 0;
- })) {
- return;
- }
-
- if (certificateListWasEmpty) {
- return;
- }
-
- q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"), new ImportResultProxyModel(results), make_tooltip(results));
- 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<ImportResultData> &results, const std::vector<ImportedGroup> &groups)
{
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>");
std::vector<ImportResult> res;
res.reserve(results.size());
std::transform(std::begin(results), std::end(results), std::back_inserter(res), [](const auto &r) {
return r.result;
});
const auto numProcessedCertificates = sum(res, &ImportResult::numConsidered);
QStringList lines;
if (numProcessedCertificates > 0 || groups.size() == 0) {
lines.push_back(headerLine.subs(i18n("Certificates")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:")).subs(numProcessedCertificates).toString());
lines.push_back(normalLine.subs(i18n("Imported:")).subs(sum(res, &ImportResult::numImported)).toString());
if (const int n = sum(res, &ImportResult::newSignatures))
lines.push_back(normalLine.subs(i18n("New signatures:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::newUserIDs))
lines.push_back(normalLine.subs(i18n("New user IDs:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numKeysWithoutUserID))
lines.push_back(normalLine.subs(i18n("Certificates without user IDs:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::newSubkeys))
lines.push_back(normalLine.subs(i18n("New subkeys:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::newRevocations))
lines.push_back(boldLine.subs(i18n("Newly revoked:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::notImported))
lines.push_back(boldLine.subs(i18n("Not imported:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numUnchanged))
lines.push_back(normalLine.subs(i18n("Unchanged:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered))
lines.push_back(normalLine.subs(i18n("Secret keys processed:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysImported))
lines.push_back(normalLine.subs(i18n("Secret keys imported:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered) - sum(res, &ImportResult::numSecretKeysImported)
- sum(res, &ImportResult::numSecretKeysUnchanged))
if (n > 0)
lines.push_back(boldLine.subs(i18n("Secret keys <em>not</em> imported:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysUnchanged))
lines.push_back(normalLine.subs(i18n("Secret keys unchanged:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numV3KeysSkipped))
lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:")).subs(n).toString());
}
if (!lines.empty()) {
lines.push_back(headerLine.subs(QLatin1StringView{"&nbsp;"}).toString());
}
if (groups.size() > 0) {
const auto newGroups = std::count_if(std::begin(groups), std::end(groups), [](const auto &g) {
return g.status == ImportedGroup::Status::New;
});
const auto updatedGroups = groups.size() - newGroups;
lines.push_back(headerLine.subs(i18n("Certificate Groups")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:")).subs(groups.size()).toString());
lines.push_back(normalLine.subs(i18n("New groups:")).subs(newGroups).toString());
lines.push_back(normalLine.subs(i18n("Updated groups:")).subs(updatedGroups).toString());
}
return lines.join(QLatin1StringView{});
}
static bool isImportFromSingleSource(const std::vector<ImportResultData> &res)
{
return (res.size() == 1) || (res.size() == 2 && res[0].id == res[1].id);
}
static QString make_message_report(const std::vector<ImportResultData> &res, const std::vector<ImportedGroup> &groups)
{
QString report{QLatin1StringView{"<html>"}};
if (res.empty()) {
report += i18n("No imports (should not happen, please report a bug).");
} else {
const QString title = isImportFromSingleSource(res) && !res.front().id.isEmpty() ? i18n("Detailed results of importing %1:", res.front().id)
: i18n("Detailed results of import:");
report += QLatin1StringView{"<p>"} + title + QLatin1StringView{"</p>"};
report += QLatin1StringView{"<p><table width=\"100%\">"};
report += make_report(res, groups);
report += QLatin1StringView{"</table></p>"};
}
report += QLatin1StringView{"</html>"};
return report;
}
// Returns false on error, true if please certify was shown.
bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp)
{
if (!Kleo::userHasCertificationKey()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "No certification key available";
return false;
}
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 = wrap_unique(GpgME::Context::createForProtocol(GpgME::OpenPGP));
if (!ctx) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto";
return false;
}
ctx->addKeyListMode(KeyListMode::WithSecret);
GpgME::Error err;
const auto key = ctx->key(fpr, err, false);
if (key.isNull() || err) {
// No such key most likely not OpenPGP
return false;
}
if (!Kleo::canBeCertified(key)) {
// key is expired or revoked
return false;
}
if (key.hasSecret()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "Secret key is available -> skipping certification";
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 = {
i18n("A phone call to the person."),
i18n("Using a business card."),
i18n("Confirming it on a trusted website."),
};
auto sel = KMessageBox::questionTwoActions(parentWidgetOrView(),
i18n("In order to mark the certificate as valid 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)"),
KGuiItem(i18nc("@action:button", "Certify")),
KStandardGuiItem::cancel(),
QStringLiteral("CertifyQuestion"));
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
QEventLoop loop;
auto cmd = new Commands::CertifyCertificateCommand(key);
cmd->setParentWidget(parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
}
return true;
}
namespace
{
/**
* Returns the Import of an OpenPGP key, if a single certificate was imported and this was an OpenPGP key.
* Otherwise, returns a null Import.
*/
auto getSingleOpenPGPImport(const std::vector<ImportResultData> &res)
{
static const Import nullImport;
if (!isImportFromSingleSource(res)) {
return nullImport;
}
const auto numImported = std::accumulate(res.cbegin(), res.cend(), 0, [](auto s, const auto &r) {
return s + r.result.numImported();
});
if (numImported > 1) {
return nullImport;
}
if ((res.size() >= 1) && (res[0].protocol == GpgME::OpenPGP) && (res[0].result.numImported() == 1) && (res[0].result.imports().size() == 1)) {
return res[0].result.imports()[0];
} else if ((res.size() == 2) && (res[1].protocol == GpgME::OpenPGP) && (res[1].result.numImported() == 1) && (res[1].result.imports().size() == 1)) {
return res[1].result.imports()[0];
}
return nullImport;
}
auto consolidatedAuditLogEntries(const std::vector<ImportResultData> &res)
{
static const QString gpg = QStringLiteral("gpg");
static const QString gpgsm = QStringLiteral("gpgsm");
if (res.size() == 1) {
return res.front().auditLog;
}
QStringList auditLogs;
auto extractAndAnnotateAuditLog = [](const ImportResultData &r) {
QString s;
if (!r.id.isEmpty()) {
const auto program = r.protocol == GpgME::OpenPGP ? gpg : gpgsm;
const auto headerLine = i18nc("file name (imported with gpg/gpgsm)", "%1 (imported with %2)", r.id, program);
s += QStringLiteral("<div><b>%1</b></div>").arg(headerLine);
}
if (r.auditLog.error().code() == GPG_ERR_NO_DATA) {
s += QStringLiteral("<em>") + i18nc("@info", "Audit log is empty.") + QStringLiteral("</em>");
} else if (r.result.error().isCanceled()) {
s += QStringLiteral("<em>") + i18nc("@info", "Import was canceled.") + QStringLiteral("</em>");
} else {
s += r.auditLog.text();
}
return s;
};
std::transform(res.cbegin(), res.cend(), std::back_inserter(auditLogs), extractAndAnnotateAuditLog);
return AuditLogEntry{auditLogs.join(QLatin1StringView{"<hr>"}), Error{}};
}
}
void ImportCertificatesCommand::Private::showDetails(const std::vector<ImportResultData> &res, const std::vector<ImportedGroup> &groups)
{
const auto singleOpenPGPImport = getSingleOpenPGPImport(res);
- setImportResultProxyModel(res);
-
if (!singleOpenPGPImport.isNull()) {
if (showPleaseCertify(singleOpenPGPImport)) {
return;
}
}
- MessageBox::information(parentWidgetOrView(), make_message_report(res, groups), consolidatedAuditLogEntries(res), i18n("Certificate Import Result"));
+
+ auto dialog = new QDialog(parentWidgetOrView());
+ auto layout = new QVBoxLayout(dialog);
+ auto label = new QLabel(make_message_report(res, groups));
+ layout->addWidget(label);
+ auto buttons = new QDialogButtonBox;
+ auto listButton = buttons->addButton(i18nc("@action:button", "List Imported Certificates"), QDialogButtonBox::ActionRole);
+ auto auditLogButton = buttons->addButton(i18nc("@action:button", "Show Audit Log"), QDialogButtonBox::ActionRole);
+ auto okButton = buttons->addButton(QDialogButtonBox::Ok);
+
+ connect(listButton, &QPushButton::clicked, listButton, [res, listButton]() {
+ auto dialog = new QDialog();
+ dialog->setWindowTitle(i18nc("@title:dialog as in 'list of certificates that were imported'", "Imported Certificates"));
+
+ const auto size = KConfigGroup(KSharedConfig::openStateConfig(), QStringLiteral("ImportedCertificatesDialog")).readEntry("Size", QSize(730, 280));
+ if (size.isValid()) {
+ dialog->resize(size);
+ }
+
+ auto layout = new QVBoxLayout(dialog);
+ auto model = AbstractKeyListModel::createFlatKeyListModel(listButton);
+ model->useKeyCache(true, KeyList::AllKeys);
+ auto proxyModel = new ImportResultProxyModel(res);
+ proxyModel->setSourceModel(model);
+ auto keyTreeView = new KeyTreeView({}, {}, proxyModel, listButton, {});
+ keyTreeView->setFlatModel(model);
+ connect(keyTreeView->view(), &QAbstractItemView::doubleClicked, keyTreeView->view(), [dialog](const auto &index) {
+ auto detailsCommand = new Commands::DetailsCommand(index.data(Kleo::KeyList::KeyRole).template value<Key>());
+ detailsCommand->setParentWidget(dialog);
+ detailsCommand->start();
+ });
+ layout->addWidget(keyTreeView);
+
+ auto buttons = new QDialogButtonBox();
+ auto closeButton = buttons->addButton(QDialogButtonBox::Close);
+ connect(closeButton, &QPushButton::clicked, closeButton, [dialog]() {
+ dialog->accept();
+ });
+ layout->addWidget(buttons);
+
+ connect(dialog, &QDialog::finished, dialog, [dialog]() {
+ KConfigGroup config(KSharedConfig::openStateConfig(), QStringLiteral("ImportedCertificatesDialog"));
+ config.writeEntry("Size", dialog->size());
+ config.sync();
+ });
+ dialog->show();
+ });
+
+ connect(auditLogButton, &QPushButton::clicked, auditLogButton, [this, res]() {
+ AuditLogViewer::showAuditLog(parentWidgetOrView(), consolidatedAuditLogEntries(res));
+ });
+
+ connect(okButton, &QPushButton::clicked, okButton, [this, dialog]() {
+ dialog->accept();
+ });
+ layout->addWidget(buttons);
+
+ dialog->show();
}
static QString make_error_message(const Error &err, const QString &id)
{
Q_ASSERT(err);
Q_ASSERT(!err.isCanceled());
if (id.isEmpty()) {
return i18n(
"<qt><p>An error occurred while trying to import the certificate:</p>"
"<p><b>%1</b></p></qt>",
Formatting::errorAsString(err));
} else {
return i18n(
"<qt><p>An error occurred while trying to import the certificate %1:</p>"
"<p><b>%2</b></p></qt>",
id,
Formatting::errorAsString(err));
}
}
void ImportCertificatesCommand::Private::showError(const ImportResultData &result)
{
MessageBox::error(parentWidgetOrView(), make_error_message(result.result.error(), result.id), result.auditLog);
}
void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait)
{
if (wait == waitForMoreJobs) {
return;
}
waitForMoreJobs = wait;
if (!waitForMoreJobs) {
tryToFinish();
}
}
void ImportCertificatesCommand::Private::onImportResult(const ImportResult &result, QGpgME::Job *finishedJob)
{
if (!finishedJob) {
finishedJob = qobject_cast<QGpgME::Job *>(q->sender());
}
Q_ASSERT(finishedJob);
qCDebug(KLEOPATRA_LOG) << q << __func__ << finishedJob;
auto it = std::find_if(std::begin(runningJobs), std::end(runningJobs), [finishedJob](const auto &job) {
return job.job == finishedJob;
});
Q_ASSERT(it != std::end(runningJobs));
if (it == std::end(runningJobs)) {
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found";
return;
}
Kleo::for_each(it->connections, &disconnectConnection);
it->connections.clear();
increaseProgressValue();
const auto job = *it;
addImportResult({job.id, job.protocol, job.type, result, AuditLogEntry::fromJob(finishedJob)}, job);
}
void ImportCertificatesCommand::Private::addImportResult(const ImportResultData &result, const ImportJobData &job)
{
qCDebug(KLEOPATRA_LOG) << q << __func__ << result.id << "Result:" << Formatting::errorAsString(result.result.error());
results.push_back(result);
if (importFailed(result)) {
showError(result);
}
if (job.job) {
const auto count = std::erase(runningJobs, job);
Q_ASSERT(count == 1);
}
tryToFinish();
}
static void handleOwnerTrust(const std::vector<ImportResultData> &results, QWidget *dialog)
{
std::unordered_set<std::string> askedAboutFingerprints;
for (const auto &r : results) {
if (r.protocol != GpgME::Protocol::OpenPGP) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping non-OpenPGP import";
continue;
}
const auto imports = r.result.imports();
for (const auto &import : imports) {
if (!(import.status() & (Import::Status::NewKey | Import::Status::ContainedSecretKey))) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping already known imported public key";
continue;
}
const char *fpr = import.fingerprint();
if (!fpr) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import without fingerprint";
continue;
}
if (Kleo::contains(askedAboutFingerprints, fpr)) {
// imports of secret keys can result in multiple Imports for the same key
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import for already handled fingerprint";
continue;
}
GpgME::Error err;
auto ctx = wrap_unique(Context::createForProtocol(GpgME::Protocol::OpenPGP));
if (!ctx) {
qCWarning(KLEOPATRA_LOG) << "Failed to get context";
continue;
}
ctx->addKeyListMode(KeyListMode::WithSecret);
const Key toTrustOwner = ctx->key(fpr, err, false);
if (toTrustOwner.isNull() || !toTrustOwner.hasSecret()) {
continue;
}
if (toTrustOwner.ownerTrust() == Key::OwnerTrust::Ultimate) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping key with ultimate ownertrust";
continue;
}
const auto toTrustOwnerUserIDs{toTrustOwner.userIDs()};
// ki18n(" ") as initializer because initializing with empty string leads to
// (I18N_EMPTY_MESSAGE)
const KLocalizedString uids = std::accumulate(toTrustOwnerUserIDs.cbegin(),
toTrustOwnerUserIDs.cend(),
KLocalizedString{ki18n(" ")},
[](KLocalizedString temp, const auto &uid) {
return kxi18nc("@info", "%1<item>%2</item>").subs(temp).subs(Formatting::prettyNameAndEMail(uid));
});
const QString str = xi18nc("@info",
"<para>You have imported a certificate with fingerprint</para>"
"<para><numid>%1</numid></para>"
"<para>"
"and user IDs"
"<list>%2</list>"
"</para>"
"<para>Is this your own certificate?</para>",
Formatting::prettyID(fpr),
uids);
int k = KMessageBox::questionTwoActionsCancel(dialog,
str,
i18nc("@title:window", "Mark Own Certificate"),
KGuiItem{i18nc("@action:button", "Yes, It's Mine")},
KGuiItem{i18nc("@action:button", "No, It's Not Mine")});
askedAboutFingerprints.insert(fpr);
if (k == KMessageBox::ButtonCode::PrimaryAction) {
// 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);
} else if (k == KMessageBox::ButtonCode::Cancel) {
// do not bother the user with further "Is this yours?" questions
return;
}
}
}
}
static void validateImportedCertificate(const GpgME::Import &import)
{
if (const auto fpr = import.fingerprint()) {
auto key = KeyCache::instance()->findByFingerprint(fpr);
if (!key.isNull()) {
// this triggers a keylisting with validation for this certificate
key.update();
} else {
qCWarning(KLEOPATRA_LOG) << __func__ << "Certificate with fingerprint" << fpr << "not found";
}
}
}
static void handleExternalCMSImports(const std::vector<ImportResultData> &results)
{
// 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 auto &r : results) {
if (r.protocol == GpgME::CMS && r.type == ImportType::External && !importFailed(r) && !importWasCanceled(r)) {
const auto imports = r.result.imports();
std::for_each(std::begin(imports), std::end(imports), &validateImportedCertificate);
}
}
}
void ImportCertificatesCommand::Private::processResults()
{
importGroups();
if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) {
importingSignerKeys = true;
const auto missingSignerKeys = getMissingSignerKeyIds(results);
if (!missingSignerKeys.empty()) {
importSignerKeys(missingSignerKeys);
return;
}
}
handleExternalCMSImports(results);
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
handleOwnerTrust(results, parentWidgetOrView());
auto hasError = std::ranges::any_of(results, [](const auto &result) {
return importFailed(result);
});
auto allAreIrrelevant = std::ranges::all_of(results, [](const auto &result) {
return result.result.numConsidered() == 0 || (importFailed(result) && result.result.numConsidered() == 1);
});
if (!(hasError && allAreIrrelevant)) {
showDetails(results, importedGroups);
}
auto tv = dynamic_cast<QTreeView *>(view());
if (!tv) {
qCDebug(KLEOPATRA_LOG) << "Failed to find treeview";
} else {
tv->expandAll();
}
finished();
}
void ImportCertificatesCommand::Private::tryToFinish()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
if (waitForMoreJobs) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "Waiting for more jobs -> keep going";
return;
}
if (!runningJobs.empty()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are unfinished jobs -> keep going";
return;
}
if (!pendingJobs.empty()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are pending jobs -> start the next one";
auto job = pendingJobs.front();
pendingJobs.pop();
job.job->startNow();
runningJobs.push_back(job);
return;
}
if (keyListConnection) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "There is already a valid keyListConnection!";
} else {
auto keyCache = KeyCache::mutableInstance();
keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone, q, [this]() {
keyCacheUpdated();
});
keyCache->startKeyListing();
}
}
void ImportCertificatesCommand::Private::keyCacheUpdated()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
if (!disconnect(keyListConnection)) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "Failed to disconnect keyListConnection";
}
keyCacheAutoRefreshSuspension.reset();
const auto allIds = std::accumulate(std::cbegin(results), std::cend(results), std::set<QString>{}, [](auto allIds, const auto &r) {
allIds.insert(r.id);
return allIds;
});
const auto canceledIds = std::accumulate(std::cbegin(results), std::cend(results), std::set<QString>{}, [](auto canceledIds, const auto &r) {
if (importWasCanceled(r)) {
canceledIds.insert(r.id);
}
return canceledIds;
});
const auto totalConsidered = std::accumulate(std::cbegin(results), std::cend(results), 0, [](auto totalConsidered, const auto &r) {
return totalConsidered + r.result.numConsidered();
});
if (totalConsidered == 0 && canceledIds.size() == allIds.size()) {
// nothing was considered for import and at least one import per id was
// canceled => treat the command as canceled
canceled();
return;
}
processResults();
}
static ImportedGroup storeGroup(const KeyGroup &group, const QString &id, QWidget *parent)
{
if (std::ranges::any_of(group.keys(), [](const auto &key) {
return !Kleo::keyHasEncrypt(key);
})) {
KMessageBox::information(
parent,
xi18nc("@info",
"<para>The imported group</para><para><emphasis>%1</emphasis></para><para>contains certificates that cannot be used for encryption. "
"This may lead to unexpected results.</para>",
group.name()));
}
const auto status = KeyCache::instance()->group(group.id()).isNull() ? ImportedGroup::Status::New : ImportedGroup::Status::Updated;
if (status == ImportedGroup::Status::New) {
KeyCache::mutableInstance()->insert(group);
} else {
KeyCache::mutableInstance()->update(group);
}
return {id, group, status};
}
void ImportCertificatesCommand::Private::importGroups()
{
for (const auto &path : filesToImportGroupsFrom) {
const bool certificateImportSucceeded = std::any_of(std::cbegin(results), std::cend(results), [path](const auto &r) {
return r.id == path && !importFailed(r) && !importWasCanceled(r);
});
if (certificateImportSucceeded) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Importing groups from file" << path;
const auto groups = readKeyGroups(path);
std::transform(std::begin(groups), std::end(groups), std::back_inserter(importedGroups), [path, this](const auto &group) {
return storeGroup(group, path, parentWidgetOrView());
});
}
increaseProgressValue();
}
filesToImportGroupsFrom.clear();
}
static auto accumulateNewKeys(std::vector<std::string> &fingerprints, const std::vector<GpgME::Import> &imports)
{
return std::accumulate(std::begin(imports), std::end(imports), fingerprints, [](auto fingerprints, const auto &import) {
if (import.status() == Import::NewKey) {
fingerprints.push_back(import.fingerprint());
}
return fingerprints;
});
}
static auto accumulateNewOpenPGPKeys(const std::vector<ImportResultData> &results)
{
return std::accumulate(std::begin(results), std::end(results), std::vector<std::string>{}, [](auto fingerprints, const auto &r) {
if (r.protocol == GpgME::OpenPGP) {
fingerprints = accumulateNewKeys(fingerprints, r.result.imports());
}
return fingerprints;
});
}
std::set<QString> ImportCertificatesCommand::Private::getMissingSignerKeyIds(const std::vector<ImportResultData> &results)
{
auto newOpenPGPKeys = KeyCache::instance()->findByFingerprint(accumulateNewOpenPGPKeys(results));
// update all new OpenPGP keys to get information about certifications
std::for_each(std::begin(newOpenPGPKeys), std::end(newOpenPGPKeys), std::mem_fn(&Key::update));
auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(newOpenPGPKeys);
return missingSignerKeyIds;
}
void ImportCertificatesCommand::Private::importSignerKeys(const std::set<QString> &keyIds)
{
Q_ASSERT(!keyIds.empty());
setProgressLabelText(i18np("Fetching 1 signer key... (this can take a while)", "Fetching %1 signer keys... (this can take a while)", keyIds.size()));
setWaitForMoreJobs(true);
// start one import per key id to allow canceling the key retrieval without
// losing already retrieved keys
for (const auto &keyId : keyIds) {
startImport(GpgME::OpenPGP, {keyId}, QStringLiteral("Retrieve Signer Keys"));
}
setWaitForMoreJobs(false);
}
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,
[[maybe_unused]] const ImportOptions &options)
{
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"));
addImportResult({id, protocol, ImportType::Local, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector<QMetaObject::Connection> connections = {
connect(job.get(),
&AbstractImportJob::result,
q,
[this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress),
};
job->setImportFilter(options.importFilter);
#if QGPGME_IMPORT_JOB_SUPPORTS_IMPORT_OPTIONS
job->setImportOptions(options.importOptions);
#endif
job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl);
const GpgME::Error err = job->startLater(data);
if (err.code()) {
addImportResult({id, protocol, ImportType::Local, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
pendingJobs.push({id, protocol, ImportType::Local, job.release(), connections});
}
}
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"));
addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector<QMetaObject::Connection> connections = {
connect(job.get(),
&AbstractImportJob::result,
q,
[this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress),
};
const GpgME::Error err = job->start(keys);
if (err.code()) {
addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
static auto get_receive_keys_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
std::unique_ptr<ReceiveKeysJob> job{};
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
job.reset(backend->receiveKeysJob());
}
return job;
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, [[maybe_unused]] const QStringList &keyIds, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
auto job = get_receive_keys_job(protocol);
if (!job.get()) {
qCWarning(KLEOPATRA_LOG) << "Failed to get ReceiveKeysJob for protocol" << Formatting::displayName(protocol);
addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector<QMetaObject::Connection> connections = {
connect(job.get(),
&AbstractImportJob::result,
q,
[this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress),
};
const GpgME::Error err = job->start(keyIds);
if (err.code()) {
addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename)
{
increaseProgressMaximum();
filesToImportGroupsFrom.push_back(filename);
}
void ImportCertificatesCommand::Private::setUpProgressDialog()
{
if (progressDialog) {
return;
}
progressDialog = new QProgressDialog{parentWidgetOrView()};
// use a non-modal progress dialog to avoid reentrancy problems (and crashes) if multiple jobs finish in the same event loop cycle
// (cf. the warning for QProgressDialog::setValue() in the API documentation)
progressDialog->setModal(false);
progressDialog->setWindowTitle(progressWindowTitle);
progressDialog->setLabelText(progressLabelText);
progressDialog->setMinimumDuration(1000);
progressDialog->setMaximum(1);
progressDialog->setValue(0);
connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel);
connect(q, &Command::finished, progressDialog, [this]() {
progressDialog->accept();
});
}
void ImportCertificatesCommand::Private::setProgressWindowTitle(const QString &title)
{
if (progressDialog) {
progressDialog->setWindowTitle(title);
} else {
progressWindowTitle = title;
}
}
void ImportCertificatesCommand::Private::setProgressLabelText(const QString &text)
{
if (progressDialog) {
progressDialog->setLabelText(text);
} else {
progressLabelText = text;
}
}
void ImportCertificatesCommand::Private::increaseProgressMaximum()
{
setUpProgressDialog();
progressDialog->setMaximum(progressDialog->maximum() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::increaseProgressValue()
{
progressDialog->setValue(progressDialog->value() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::setProgressToMaximum()
{
qCDebug(KLEOPATRA_LOG) << __func__;
progressDialog->setValue(progressDialog->maximum());
}
void ImportCertificatesCommand::doCancel()
{
const auto jobsToCancel = d->runningJobs;
std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel), [this](const auto &job) {
if (!job.connections.empty()) {
// ignore jobs without connections; they are already completed
qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job;
job.job->slotCancel();
d->onImportResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job);
}
});
}
#undef d
#undef q
#include "importcertificatescommand.moc"
#include "moc_importcertificatescommand.cpp"
diff --git a/src/view/tabwidget.cpp b/src/view/tabwidget.cpp
index 5f0018d3f..3be065159 100644
--- a/src/view/tabwidget.cpp
+++ b/src/view/tabwidget.cpp
@@ -1,1167 +1,1133 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "tabwidget.h"
#include "kleopatra_debug.h"
#include "searchbar.h"
#include <settings.h>
#include <utils/action_data.h>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Stl_Util>
#include <Libkleo/UserIDProxyModel>
#include <gpgme++/key.h>
// needed for GPGME_VERSION_NUMBER
#include <gpgme.h>
#include <KActionCollection>
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <QAction>
#include <QInputDialog>
#include <QTabWidget>
#include <QAbstractProxyModel>
#include <QHeaderView>
#include <QMenu>
#include <QRegularExpression>
#include <QTimer>
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <map>
using namespace Kleo;
using namespace GpgME;
namespace
{
class Page : public Kleo::KeyTreeView
{
Q_OBJECT
Page(const Page &other);
public:
Page(const QString &title,
const QString &id,
const QString &text,
AbstractKeyListSortFilterProxyModel *proxy = nullptr,
const QString &toolTip = QString(),
QWidget *parent = nullptr,
const KConfigGroup &group = KConfigGroup(),
KeyTreeView::Options options = KeyTreeView::Default);
Page(const KConfigGroup &group, KeyTreeView::Options options = KeyTreeView::Default, QWidget *parent = nullptr);
~Page() override;
- void setTemporary(bool temporary);
- bool isTemporary() const
- {
- return m_isTemporary;
- }
-
void setHierarchicalView(bool hierarchical) override;
void setStringFilter(const QString &filter) override;
void setKeyFilter(const std::shared_ptr<KeyFilter> &filter) override;
QString title() const
{
return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title;
}
void setTitle(const QString &title);
QString toolTip() const
{
return m_toolTip.isEmpty() ? title() : m_toolTip;
}
// not used void setToolTip(const QString &tip);
bool canBeClosed() const
{
return m_canBeClosed;
}
bool canBeRenamed() const
{
return m_canBeRenamed;
}
bool canChangeStringFilter() const
{
return m_canChangeStringFilter;
}
bool canChangeKeyFilter() const
{
- return m_canChangeKeyFilter && !m_isTemporary;
+ return m_canChangeKeyFilter;
}
bool canChangeHierarchical() const
{
return m_canChangeHierarchical;
}
Page *clone() const override
{
return new Page(*this);
}
void liftAllRestrictions()
{
m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true;
}
void closePage()
{
m_configGroup.deleteGroup();
m_configGroup.sync();
}
KConfigGroup configGroup() const
{
return m_configGroup;
}
Q_SIGNALS:
void titleChanged(const QString &title);
private:
void init();
private:
QString m_title;
QString m_toolTip;
- bool m_isTemporary : 1;
bool m_canBeClosed : 1;
bool m_canBeRenamed : 1;
bool m_canChangeStringFilter : 1;
bool m_canChangeKeyFilter : 1;
bool m_canChangeHierarchical : 1;
KConfigGroup m_configGroup;
};
} // anon namespace
Page::Page(const Page &other)
: KeyTreeView(other)
, m_title(other.m_title)
, m_toolTip(other.m_toolTip)
- , m_isTemporary(other.m_isTemporary)
, m_canBeClosed(other.m_canBeClosed)
, m_canBeRenamed(other.m_canBeRenamed)
, m_canChangeStringFilter(other.m_canChangeStringFilter)
, m_canChangeKeyFilter(other.m_canChangeKeyFilter)
, m_canChangeHierarchical(other.m_canChangeHierarchical)
, m_configGroup(other.configGroup().config()->group(QUuid::createUuid().toString()))
{
init();
}
Page::Page(const QString &title,
const QString &id,
const QString &text,
AbstractKeyListSortFilterProxyModel *proxy,
const QString &toolTip,
QWidget *parent,
const KConfigGroup &group,
KeyTreeView::Options options)
: KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group, options)
, m_title(title)
, m_toolTip(toolTip)
- , m_isTemporary(false)
, m_canBeClosed(true)
, m_canBeRenamed(true)
, m_canChangeStringFilter(true)
, m_canChangeKeyFilter(true)
, m_canChangeHierarchical(true)
, m_configGroup(group)
{
init();
}
static const char TITLE_ENTRY[] = "title";
static const char STRING_FILTER_ENTRY[] = "string-filter";
static const char KEY_FILTER_ENTRY[] = "key-filter";
static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view";
static const char COLUMN_SIZES[] = "column-sizes";
static const char SORT_COLUMN[] = "sort-column";
static const char SORT_DESCENDING[] = "sort-descending";
Page::Page(const KConfigGroup &group, KeyTreeView::Options options, QWidget *parent)
: KeyTreeView(group.readEntry(STRING_FILTER_ENTRY),
KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)),
nullptr,
parent,
group,
options)
, m_title(group.readEntry(TITLE_ENTRY))
, m_toolTip()
- , m_isTemporary(false)
, m_canBeClosed(!group.isImmutable())
, m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY))
, m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY))
, m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY))
, m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY))
, m_configGroup(group)
{
init();
setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true));
const QList<int> settings = group.readEntry(COLUMN_SIZES, QList<int>());
std::vector<int> sizes;
sizes.reserve(settings.size());
std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes));
setColumnSizes(sizes);
setSortColumn(group.readEntry(SORT_COLUMN, 0), group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder);
}
void Page::init()
{
// check for GpgME >= 1.24.0
#if GPGME_VERSION_NUMBER >= 0x011800 && !defined(Q_OS_WIN)
view()->setDragDropMode(QAbstractItemView::DragOnly);
view()->setDragEnabled(true);
#endif
}
Page::~Page()
{
}
void Page::setStringFilter(const QString &filter)
{
if (!m_canChangeStringFilter) {
return;
}
KeyTreeView::setStringFilter(filter);
m_configGroup.writeEntry(STRING_FILTER_ENTRY, stringFilter());
m_configGroup.sync();
}
void Page::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (!canChangeKeyFilter()) {
return;
}
const QString oldTitle = title();
KeyTreeView::setKeyFilter(filter);
const QString newTitle = title();
if (oldTitle != newTitle) {
Q_EMIT titleChanged(newTitle);
}
m_configGroup.writeEntry(KEY_FILTER_ENTRY, keyFilter() ? keyFilter()->id() : QString());
m_configGroup.sync();
}
void Page::setTitle(const QString &t)
{
if (t == m_title) {
return;
}
if (!m_canBeRenamed) {
return;
}
const QString oldTitle = title();
m_title = t;
const QString newTitle = title();
if (oldTitle != newTitle) {
Q_EMIT titleChanged(newTitle);
m_configGroup.writeEntry(TITLE_ENTRY, m_title);
m_configGroup.sync();
}
}
#if 0 // not used
void Page::setToolTip(const QString &tip)
{
if (tip == m_toolTip) {
return;
}
if (!m_canBeRenamed) {
return;
}
const QString oldTip = toolTip();
m_toolTip = tip;
const QString newTip = toolTip();
if (oldTip != newTip) {
Q_EMIT titleChanged(title());
}
}
#endif
void Page::setHierarchicalView(bool on)
{
if (!m_canChangeHierarchical) {
return;
}
KeyTreeView::setHierarchicalView(on);
m_configGroup.writeEntry(HIERARCHICAL_VIEW_ENTRY, on);
m_configGroup.sync();
}
-void Page::setTemporary(bool on)
-{
- if (on == m_isTemporary) {
- return;
- }
- m_isTemporary = on;
- if (on) {
- setKeyFilter(std::shared_ptr<KeyFilter>());
- }
-}
-
namespace
{
class Actions
{
public:
constexpr static const char *Rename = "window_rename_tab";
constexpr static const char *Duplicate = "window_duplicate_tab";
constexpr static const char *Close = "window_close_tab";
constexpr static const char *MoveLeft = "window_move_tab_left";
constexpr static const char *MoveRight = "window_move_tab_right";
constexpr static const char *Hierarchical = "window_view_hierarchical";
constexpr static const char *ExpandAll = "window_expand_all";
constexpr static const char *CollapseAll = "window_collapse_all";
explicit Actions()
{
}
void insert(const std::string &name, QAction *action)
{
actions.insert({name, action});
}
auto get(const std::string &name) const
{
const auto it = actions.find(name);
return (it != actions.end()) ? it->second : nullptr;
}
void setChecked(const std::string &name, bool checked) const
{
if (auto action = get(name)) {
action->setChecked(checked);
}
}
void setEnabled(const std::string &name, bool enabled) const
{
if (auto action = get(name)) {
action->setEnabled(enabled);
}
}
void setVisible(const std::string &name, bool visible) const
{
if (auto action = get(name)) {
action->setVisible(visible);
}
}
private:
std::map<std::string, QAction *> actions;
};
}
//
//
// TabWidget
//
//
class TabWidget::Private
{
friend class ::Kleo::TabWidget;
TabWidget *const q;
public:
explicit Private(TabWidget *qq, KeyTreeView::Options options);
~Private()
{
}
private:
void slotContextMenu(const QPoint &p);
void currentIndexChanged(int index);
void slotPageTitleChanged(const QString &title);
void slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &filter);
void slotPageStringFilterChanged(const QString &filter);
void slotPageHierarchyChanged(bool on);
#ifndef QT_NO_INPUTDIALOG
void slotRenameCurrentTab()
{
renamePage(currentPage());
}
#endif // QT_NO_INPUTDIALOG
void slotNewTab();
void slotDuplicateCurrentTab()
{
duplicatePage(currentPage());
}
void slotCloseCurrentTab()
{
closePage(currentPage());
}
void slotMoveCurrentTabLeft()
{
movePageLeft(currentPage());
}
void slotMoveCurrentTabRight()
{
movePageRight(currentPage());
}
void slotToggleHierarchicalView(bool on)
{
toggleHierarchicalView(currentPage(), on);
}
void slotExpandAll()
{
expandAll(currentPage());
}
void slotCollapseAll()
{
collapseAll(currentPage());
}
#ifndef QT_NO_INPUTDIALOG
void renamePage(Page *page);
#endif
void duplicatePage(Page *page);
void closePage(Page *page);
void movePageLeft(Page *page);
void movePageRight(Page *page);
void toggleHierarchicalView(Page *page, bool on);
void expandAll(Page *page);
void collapseAll(Page *page);
void enableDisableCurrentPageActions();
void enableDisablePageActions(const Actions &actions, const Page *page);
Page *currentPage() const
{
Q_ASSERT(!tabWidget->currentWidget() || qobject_cast<Page *>(tabWidget->currentWidget()));
return static_cast<Page *>(tabWidget->currentWidget());
}
Page *page(unsigned int idx) const
{
Q_ASSERT(!tabWidget->widget(idx) || qobject_cast<Page *>(tabWidget->widget(idx)));
return static_cast<Page *>(tabWidget->widget(idx));
}
Page *senderPage() const
{
QObject *const sender = q->sender();
Q_ASSERT(!sender || qobject_cast<Page *>(sender));
return static_cast<Page *>(sender);
}
bool isSenderCurrentPage() const
{
Page *const sp = senderPage();
return sp && sp == currentPage();
}
QTreeView *addView(Page *page, Page *columnReference);
private:
AbstractKeyListModel *flatModel = nullptr;
AbstractKeyListModel *hierarchicalModel = nullptr;
QToolButton *newTabButton = nullptr;
QToolButton *closeTabButton = nullptr;
QTabWidget *tabWidget = nullptr;
QAction *newAction = nullptr;
Actions currentPageActions;
Actions otherPageActions;
bool actionsCreated = false;
KSharedConfig::Ptr config;
QString configKey;
KeyTreeView::Options keyTreeViewOptions;
};
TabWidget::Private::Private(TabWidget *qq, KeyTreeView::Options options)
: q{qq}
, keyTreeViewOptions(options)
{
auto layout = new QVBoxLayout{q};
layout->setContentsMargins(0, 0, 0, 0);
// create "New Tab" button before tab widget to ensure correct tab order
newTabButton = new QToolButton{q};
tabWidget = new QTabWidget{q};
Q_SET_OBJECT_NAME(tabWidget);
layout->addWidget(tabWidget);
tabWidget->setMovable(true);
tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
// create "Close Tab" button after tab widget to ensure correct tab order
closeTabButton = new QToolButton{q};
connect(tabWidget, &QTabWidget::currentChanged, q, [this](int index) {
currentIndexChanged(index);
});
connect(tabWidget->tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint &p) {
slotContextMenu(p);
});
connect(tabWidget->tabBar(), &QTabBar::tabMoved, q, [this]() {
q->saveViews();
});
}
void TabWidget::Private::slotContextMenu(const QPoint &p)
{
const int tabUnderPos = tabWidget->tabBar()->tabAt(p);
Page *const contextMenuPage = static_cast<Page *>(tabWidget->widget(tabUnderPos));
const Page *const current = currentPage();
const auto actions = contextMenuPage == current ? currentPageActions : otherPageActions;
enableDisablePageActions(actions, contextMenuPage);
QMenu menu;
if (auto action = actions.get(Actions::Rename)) {
menu.addAction(action);
}
menu.addSeparator();
menu.addAction(newAction);
if (auto action = actions.get(Actions::Duplicate)) {
menu.addAction(action);
}
menu.addSeparator();
if (auto action = actions.get(Actions::MoveLeft)) {
menu.addAction(action);
}
if (auto action = actions.get(Actions::MoveRight)) {
menu.addAction(action);
}
menu.addSeparator();
if (auto action = actions.get(Actions::Close)) {
menu.addAction(action);
}
const QAction *const action = menu.exec(tabWidget->tabBar()->mapToGlobal(p));
if (!action) {
return;
}
if (contextMenuPage == current || action == newAction) {
return; // performed through signal/slot connections...
}
#ifndef QT_NO_INPUTDIALOG
if (action == otherPageActions.get(Actions::Rename)) {
renamePage(contextMenuPage);
}
#endif // QT_NO_INPUTDIALOG
else if (action == otherPageActions.get(Actions::Duplicate)) {
duplicatePage(contextMenuPage);
} else if (action == otherPageActions.get(Actions::Close)) {
closePage(contextMenuPage);
} else if (action == otherPageActions.get(Actions::MoveLeft)) {
movePageLeft(contextMenuPage);
} else if (action == otherPageActions.get(Actions::MoveRight)) {
movePageRight(contextMenuPage);
}
}
void TabWidget::Private::currentIndexChanged(int index)
{
const Page *const page = this->page(index);
Q_EMIT q->currentViewChanged(page ? page->view() : nullptr);
Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr<KeyFilter>());
Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString());
enableDisableCurrentPageActions();
}
void TabWidget::Private::enableDisableCurrentPageActions()
{
const Page *const page = currentPage();
Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter());
Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter());
enableDisablePageActions(currentPageActions, page);
}
void TabWidget::Private::enableDisablePageActions(const Actions &actions, const Page *p)
{
actions.setEnabled(Actions::Rename, p && p->canBeRenamed());
actions.setEnabled(Actions::Duplicate, p);
actions.setEnabled(Actions::Close, p && p->canBeClosed() && tabWidget->count() > 1);
actions.setEnabled(Actions::MoveLeft, p && tabWidget->indexOf(const_cast<Page *>(p)) != 0);
actions.setEnabled(Actions::MoveRight, p && tabWidget->indexOf(const_cast<Page *>(p)) != tabWidget->count() - 1);
actions.setEnabled(Actions::Hierarchical, p && p->canChangeHierarchical());
actions.setChecked(Actions::Hierarchical, p && p->isHierarchicalView());
actions.setVisible(Actions::Hierarchical, Kleo::Settings{}.cmsEnabled());
actions.setEnabled(Actions::ExpandAll, p && p->isHierarchicalView());
actions.setEnabled(Actions::CollapseAll, p && p->isHierarchicalView());
}
void TabWidget::Private::slotPageTitleChanged(const QString &)
{
if (Page *const page = senderPage()) {
const int idx = tabWidget->indexOf(page);
tabWidget->setTabText(idx, page->title());
tabWidget->setTabToolTip(idx, page->toolTip());
}
}
void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &kf)
{
if (isSenderCurrentPage()) {
Q_EMIT q->keyFilterChanged(kf);
}
}
void TabWidget::Private::slotPageStringFilterChanged(const QString &filter)
{
if (isSenderCurrentPage()) {
Q_EMIT q->stringFilterChanged(filter);
}
}
void TabWidget::Private::slotPageHierarchyChanged(bool)
{
enableDisableCurrentPageActions();
}
void TabWidget::Private::slotNewTab()
{
auto group = KSharedConfig::openStateConfig()->group(QStringLiteral("%1:View %2").arg(configKey, QUuid::createUuid().toString()));
Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group, keyTreeViewOptions);
group.writeEntry(KEY_FILTER_ENTRY, QStringLiteral("all-certificates"));
group.sync();
addView(page, currentPage());
tabWidget->setCurrentIndex(tabWidget->count() - 1);
q->saveViews();
}
void TabWidget::Private::renamePage(Page *page)
{
if (!page) {
return;
}
bool ok;
const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok);
if (!ok) {
return;
}
page->setTitle(text);
}
void TabWidget::Private::duplicatePage(Page *page)
{
if (!page) {
return;
}
Page *const clone = page->clone();
Q_ASSERT(clone);
clone->liftAllRestrictions();
addView(clone, page);
}
void TabWidget::Private::closePage(Page *page)
{
if (!page || !page->canBeClosed() || tabWidget->count() <= 1) {
return;
}
Q_EMIT q->viewAboutToBeRemoved(page->view());
page->closePage();
tabWidget->removeTab(tabWidget->indexOf(page));
q->saveViews();
enableDisableCurrentPageActions();
}
void TabWidget::Private::movePageLeft(Page *page)
{
if (!page) {
return;
}
const int idx = tabWidget->indexOf(page);
if (idx <= 0) {
return;
}
tabWidget->tabBar()->moveTab(idx, idx - 1);
enableDisableCurrentPageActions();
}
void TabWidget::Private::movePageRight(Page *page)
{
if (!page) {
return;
}
const int idx = tabWidget->indexOf(page);
if (idx < 0 || idx >= tabWidget->count() - 1) {
return;
}
tabWidget->tabBar()->moveTab(idx, idx + 1);
enableDisableCurrentPageActions();
}
void TabWidget::Private::toggleHierarchicalView(Page *page, bool on)
{
if (!page) {
return;
}
page->setHierarchicalView(on);
}
void TabWidget::Private::expandAll(Page *page)
{
if (!page || !page->view()) {
return;
}
page->view()->expandAll();
}
void TabWidget::Private::collapseAll(Page *page)
{
if (!page || !page->view()) {
return;
}
page->view()->collapseAll();
}
TabWidget::TabWidget(KeyTreeView::Options options, QWidget *p, Qt::WindowFlags f)
: QWidget(p, f)
, d(new Private(this, options))
{
}
TabWidget::~TabWidget()
{
saveViews();
}
void TabWidget::setFlatModel(AbstractKeyListModel *model)
{
if (model == d->flatModel) {
return;
}
d->flatModel = model;
for (unsigned int i = 0, end = count(); i != end; ++i)
if (Page *const page = d->page(i)) {
page->setFlatModel(model);
}
}
AbstractKeyListModel *TabWidget::flatModel() const
{
return d->flatModel;
}
void TabWidget::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == d->hierarchicalModel) {
return;
}
d->hierarchicalModel = model;
for (unsigned int i = 0, end = count(); i != end; ++i)
if (Page *const page = d->page(i)) {
page->setHierarchicalModel(model);
}
}
AbstractKeyListModel *TabWidget::hierarchicalModel() const
{
return d->hierarchicalModel;
}
QString TabWidget::stringFilter() const
{
return d->currentPage() ? d->currentPage()->stringFilter() : QString{};
}
void TabWidget::setStringFilter(const QString &filter)
{
if (Page *const page = d->currentPage()) {
page->setStringFilter(filter);
}
}
void TabWidget::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (!filter) {
qCDebug(KLEOPATRA_LOG) << "TabWidget::setKeyFilter() trial to set filter=NULL";
return;
}
if (Page *const page = d->currentPage()) {
page->setKeyFilter(filter);
}
}
std::vector<QAbstractItemView *> TabWidget::views() const
{
std::vector<QAbstractItemView *> result;
const unsigned int N = count();
result.reserve(N);
for (unsigned int i = 0; i != N; ++i)
if (const Page *const p = d->page(i)) {
result.push_back(p->view());
}
return result;
}
QAbstractItemView *TabWidget::currentView() const
{
if (Page *const page = d->currentPage()) {
return page->view();
} else {
return nullptr;
}
}
KeyListModelInterface *TabWidget::currentModel() const
{
const QAbstractItemView *const view = currentView();
if (!view) {
return nullptr;
}
auto const proxy = qobject_cast<QAbstractProxyModel *>(view->model());
if (!proxy) {
return nullptr;
}
return dynamic_cast<KeyListModelInterface *>(proxy);
}
unsigned int TabWidget::count() const
{
return d->tabWidget->count();
}
void TabWidget::setMultiSelection(bool on)
{
for (unsigned int i = 0, end = count(); i != end; ++i)
if (const Page *const p = d->page(i))
if (QTreeView *const view = p->view()) {
view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection);
}
}
void TabWidget::createActions(KActionCollection *coll)
{
if (!coll) {
return;
}
const action_data actionDataNew = {
"window_new_tab",
i18n("New Tab"),
i18n("Open a new tab"),
"tab-new-background",
this,
[this](bool) {
d->slotNewTab();
},
QStringLiteral("CTRL+SHIFT+N"),
};
d->newAction = make_action_from_data(actionDataNew, coll);
const std::vector<action_data> actionData = {
{
Actions::Rename,
i18n("Rename Tab..."),
i18n("Rename this tab"),
"edit-rename",
this,
[this](bool) {
d->slotRenameCurrentTab();
},
QStringLiteral("CTRL+SHIFT+R"),
RegularQAction,
Disabled,
},
{
Actions::Duplicate,
i18n("Duplicate Tab"),
i18n("Duplicate this tab"),
"tab-duplicate",
this,
[this](bool) {
d->slotDuplicateCurrentTab();
},
QStringLiteral("CTRL+SHIFT+D"),
},
{
Actions::Close,
i18n("Close Tab"),
i18n("Close this tab"),
"tab-close",
this,
[this](bool) {
d->slotCloseCurrentTab();
},
QStringLiteral("CTRL+SHIFT+W"),
RegularQAction,
Disabled,
}, // ### CTRL-W when available
{
Actions::MoveLeft,
i18n("Move Tab Left"),
i18n("Move this tab left"),
nullptr,
this,
[this](bool) {
d->slotMoveCurrentTabLeft();
},
QStringLiteral("CTRL+SHIFT+LEFT"),
RegularQAction,
Disabled,
},
{
Actions::MoveRight,
i18n("Move Tab Right"),
i18n("Move this tab right"),
nullptr,
this,
[this](bool) {
d->slotMoveCurrentTabRight();
},
QStringLiteral("CTRL+SHIFT+RIGHT"),
RegularQAction,
Disabled,
},
{
Actions::Hierarchical,
i18n("Hierarchical Certificate List"),
QString(),
nullptr,
this,
[this](bool on) {
d->slotToggleHierarchicalView(on);
},
QString(),
KFToggleAction,
Disabled,
},
{
Actions::ExpandAll,
i18n("Expand All"),
QString(),
nullptr,
this,
[this](bool) {
d->slotExpandAll();
},
QStringLiteral("CTRL+."),
RegularQAction,
Disabled,
},
{
Actions::CollapseAll,
i18n("Collapse All"),
QString(),
nullptr,
this,
[this](bool) {
d->slotCollapseAll();
},
QStringLiteral("CTRL+,"),
RegularQAction,
Disabled,
},
};
for (const auto &ad : actionData) {
d->currentPageActions.insert(ad.name, make_action_from_data(ad, coll));
}
for (const auto &ad : actionData) {
// create actions for the context menu of the currently not active tabs,
// but do not add those actions to the action collection
auto action = new QAction(ad.text, coll);
if (ad.icon) {
action->setIcon(QIcon::fromTheme(QLatin1StringView(ad.icon)));
}
action->setEnabled(ad.actionState == Enabled);
d->otherPageActions.insert(ad.name, action);
}
d->newTabButton->setDefaultAction(d->newAction);
d->tabWidget->setCornerWidget(d->newTabButton, Qt::TopLeftCorner);
if (auto action = d->currentPageActions.get(Actions::Close)) {
d->closeTabButton->setDefaultAction(action);
d->tabWidget->setCornerWidget(d->closeTabButton, Qt::TopRightCorner);
} else {
d->closeTabButton->setVisible(false);
}
d->actionsCreated = true;
}
QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text)
{
auto group = KSharedConfig::openStateConfig()->group(QStringLiteral("%1:View %2").arg(d->configKey, QUuid::createUuid().toString()));
Page *page = new Page(title, id, text, nullptr, QString(), nullptr, group, d->keyTreeViewOptions);
group.writeEntry(KEY_FILTER_ENTRY, id);
group.sync();
QMetaObject::invokeMethod(
this,
[page, group]() {
page->restoreLayout(group);
},
Qt::QueuedConnection);
return d->addView(page, d->currentPage());
}
QAbstractItemView *TabWidget::addView(const KConfigGroup &group, Options options)
{
Page *page = nullptr;
if (options & ShowUserIDs) {
page = new Page(group.readEntry(TITLE_ENTRY),
group.readEntry(KEY_FILTER_ENTRY),
group.readEntry(STRING_FILTER_ENTRY),
new UserIDProxyModel(this),
{},
nullptr,
group,
d->keyTreeViewOptions);
} else {
page = new Page(group, d->keyTreeViewOptions);
}
QMetaObject::invokeMethod(
this,
[page, group]() {
page->restoreLayout(group);
},
Qt::QueuedConnection);
return d->addView(page, nullptr);
}
-QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip)
-{
- const KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KeyTreeView_default"));
- Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip, nullptr, group, d->keyTreeViewOptions);
- page->setTemporary(true);
- QAbstractItemView *v = d->addView(page, d->currentPage());
- d->tabWidget->setCurrentIndex(d->tabWidget->count() - 1);
- return v;
-}
-
QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference)
{
if (!page) {
return nullptr;
}
if (!actionsCreated) {
auto coll = new KActionCollection(q);
q->createActions(coll);
}
page->setFlatModel(flatModel);
page->setHierarchicalModel(hierarchicalModel);
connect(page, &Page::titleChanged, q, [this](const QString &text) {
slotPageTitleChanged(text);
});
connect(page, &Page::keyFilterChanged, q, [this](const std::shared_ptr<Kleo::KeyFilter> &filter) {
slotPageKeyFilterChanged(filter);
});
connect(page, &Page::stringFilterChanged, q, [this](const QString &text) {
slotPageStringFilterChanged(text);
});
connect(page, &Page::hierarchicalChanged, q, [this](bool on) {
slotPageHierarchyChanged(on);
});
if (columnReference) {
QMetaObject::invokeMethod(
q,
[=]() {
page->setColumnSizes(columnReference->columnSizes());
page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder());
page->view()->saveColumnLayout(page->configGroup().name());
},
Qt::QueuedConnection);
for (auto i = 0; i < columnReference->view()->model()->columnCount(); i++) {
page->view()->setColumnHidden(i, columnReference->view()->isColumnHidden(i));
page->view()->header()->moveSection(page->view()->header()->visualIndex(i), columnReference->view()->header()->visualIndex(i));
}
for (auto row = 0; row < page->view()->model()->rowCount(); row++) {
page->view()->setExpanded(page->view()->model()->index(row, 0),
columnReference->view()->isExpanded(columnReference->view()->model()->index(row, 0)));
}
}
QAbstractItemView *const previous = q->currentView();
const int tabIndex = tabWidget->addTab(page, page->title());
setTabOrder(closeTabButton, page->view());
tabWidget->setTabToolTip(tabIndex, page->toolTip());
// work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted
QAbstractItemView *const current = q->currentView();
if (previous != current) {
currentIndexChanged(tabWidget->currentIndex());
}
enableDisableCurrentPageActions();
QTreeView *view = page->view();
Q_EMIT q->viewAdded(view);
return view;
}
static QStringList extractViewGroups(const KConfigGroup &config)
{
return config.readEntry("Tabs", QStringList());
}
void TabWidget::loadViews(const KSharedConfig::Ptr &config, const QString &configKey, Options options)
{
d->config = config;
d->configKey = configKey;
QStringList groupList = extractViewGroups(config->group(configKey));
for (const QString &view : std::as_const(groupList)) {
addView(KConfigGroup(config, view), options);
}
if (!count()) {
// add default view:
addView({}, QStringLiteral("all-certificates"));
}
}
void TabWidget::saveViews()
{
if (!d->config) {
return;
}
QStringList tabs;
for (unsigned int i = 0, end = count(); i != end; ++i) {
if (Page *const p = d->page(i)) {
- if (p->isTemporary()) {
- continue;
- }
tabs += p->configGroup().name();
}
}
d->config->group(d->configKey).writeEntry("Tabs", tabs);
d->config->sync();
}
void TabWidget::connectSearchBar(SearchBar *sb)
{
connect(sb, &SearchBar::stringFilterChanged, this, &TabWidget::setStringFilter);
connect(this, &TabWidget::stringFilterChanged, sb, &SearchBar::setStringFilter);
connect(sb, &SearchBar::keyFilterChanged, this, &TabWidget::setKeyFilter);
connect(this, &TabWidget::keyFilterChanged, sb, &SearchBar::setKeyFilter);
connect(this, &TabWidget::enableChangeStringFilter, sb, &SearchBar::setChangeStringFilterEnabled);
connect(this, &TabWidget::enableChangeKeyFilter, sb, &SearchBar::setChangeKeyFilterEnabled);
}
#include "moc_tabwidget.cpp"
#include "tabwidget.moc"
diff --git a/src/view/tabwidget.h b/src/view/tabwidget.h
index ad2a831d4..9b7953970 100644
--- a/src/view/tabwidget.h
+++ b/src/view/tabwidget.h
@@ -1,92 +1,90 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "keytreeview.h"
#include <KSharedConfig>
#include <QWidget>
#include <memory>
#include <vector>
class QAbstractItemView;
class KConfigGroup;
class KActionCollection;
namespace Kleo
{
class AbstractKeyListModel;
class AbstractKeyListSortFilterProxyModel;
class KeyFilter;
class KeyListModelInterface;
class SearchBar;
class TabWidget : public QWidget
{
Q_OBJECT
public:
enum Option {
ShowKeys = 0x00,
ShowUserIDs = 0x01,
};
Q_DECLARE_FLAGS(Options, Option)
explicit TabWidget(KeyTreeView::Options options = KeyTreeView::Option::Default, QWidget *parent = nullptr, Qt::WindowFlags f = {});
~TabWidget() override;
void setFlatModel(AbstractKeyListModel *model);
AbstractKeyListModel *flatModel() const;
void setHierarchicalModel(AbstractKeyListModel *model);
AbstractKeyListModel *hierarchicalModel() const;
QAbstractItemView *addView(const QString &title = QString(), const QString &keyFilterID = QString(), const QString &searchString = QString());
QAbstractItemView *addView(const KConfigGroup &group, Options options);
- QAbstractItemView *
- addTemporaryView(const QString &title = QString(), AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &tabToolTip = QString());
void loadViews(const KSharedConfig::Ptr &config, const QString &configKeys, Options options = ShowKeys);
void saveViews();
std::vector<QAbstractItemView *> views() const;
QAbstractItemView *currentView() const;
KeyListModelInterface *currentModel() const;
unsigned int count() const;
void createActions(KActionCollection *collection);
void connectSearchBar(SearchBar *sb);
void setMultiSelection(bool on);
QString stringFilter() const;
public Q_SLOTS:
void setKeyFilter(const std::shared_ptr<Kleo::KeyFilter> &filter);
void setStringFilter(const QString &filter);
Q_SIGNALS:
void viewAdded(QAbstractItemView *view);
void viewAboutToBeRemoved(QAbstractItemView *view);
void currentViewChanged(QAbstractItemView *view);
void stringFilterChanged(const QString &filter);
void keyFilterChanged(const std::shared_ptr<Kleo::KeyFilter> &filter);
void enableChangeStringFilter(bool enable);
void enableChangeKeyFilter(bool enable);
private:
class Private;
const std::unique_ptr<Private> d;
};
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Dec 21, 10:28 PM (1 d, 1 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
8f/23/222c48253a5f9bd0acddf79ecc86

Event Timeline