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