Page MenuHome GnuPG

No OneTemporary

diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index df59971b..7097654a 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -1,42 +1,43 @@
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
include(ECMAddTests)
find_package(Qt5Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT Qt5Test_FOUND)
message(STATUS "Qt5Test not found, autotests will not be built.")
return()
endif()
ecm_add_test(
flatkeylistmodeltest.cpp
abstractkeylistmodeltest.cpp
TEST_NAME flatkeylistmodeltest
LINK_LIBRARIES KF5::Libkleo Qt::Test
)
ecm_add_test(
hierarchicalkeylistmodeltest.cpp
abstractkeylistmodeltest.cpp
TEST_NAME hierarchicalkeylistmodeltest
LINK_LIBRARIES KF5::Libkleo Qt::Test
)
ecm_add_test(
keyresolvercoretest.cpp
keyresolvercoretest.qrc
TEST_NAME keyresolvercoretest
LINK_LIBRARIES KF5::Libkleo Qt::Test
)
ecm_add_tests(
editdirectoryservicedialogtest.cpp
LINK_LIBRARIES KF5::Libkleo KF5::WidgetsAddons Qt::Widgets Qt::Test
)
ecm_add_tests(
+ keyselectioncombotest.cpp
keyserverconfigtest.cpp
newkeyapprovaldialogtest.cpp
LINK_LIBRARIES KF5::Libkleo Qt::Widgets Qt::Test
)
diff --git a/autotests/keyselectioncombotest.cpp b/autotests/keyselectioncombotest.cpp
new file mode 100644
index 00000000..2cf85cfa
--- /dev/null
+++ b/autotests/keyselectioncombotest.cpp
@@ -0,0 +1,208 @@
+/*
+ autotests/keyselectioncombotest.cpp
+
+ This file is part of libkleopatra's test suite.
+ SPDX-FileCopyrightText: 2021 g10 Code GmbH
+ SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <Libkleo/Formatting>
+#include <Libkleo/KeyCache>
+#include <Libkleo/KeySelectionCombo>
+
+#include <QSignalSpy>
+#include <QTest>
+
+#include <gpgme++/key.h>
+#include <gpgme++/keylistresult.h>
+
+#include <gpgme.h>
+
+#include <memory>
+
+using namespace Kleo;
+
+namespace
+{
+
+auto mapValidity(GpgME::UserID::Validity validity)
+{
+ switch (validity) {
+ default:
+ case GpgME::UserID::Unknown: return GPGME_VALIDITY_UNKNOWN;
+ case GpgME::UserID::Undefined: return GPGME_VALIDITY_UNDEFINED;
+ case GpgME::UserID::Never: return GPGME_VALIDITY_NEVER;
+ case GpgME::UserID::Marginal: return GPGME_VALIDITY_MARGINAL;
+ case GpgME::UserID::Full: return GPGME_VALIDITY_FULL;
+ case GpgME::UserID::Ultimate: return GPGME_VALIDITY_ULTIMATE;
+ }
+}
+
+GpgME::Key createTestKey(const char *uid, GpgME::Protocol protocol = GpgME::UnknownProtocol, KeyUsage usage = KeyUsage::AnyUsage,
+ GpgME::UserID::Validity validity = GpgME::UserID::Full)
+{
+ static int count = 0;
+ count++;
+
+ gpgme_key_t key;
+ gpgme_key_from_uid(&key, uid);
+ Q_ASSERT(key);
+ Q_ASSERT(key->uids);
+ if (protocol != GpgME::UnknownProtocol) {
+ key->protocol = protocol == GpgME::OpenPGP ? GPGME_PROTOCOL_OpenPGP : GPGME_PROTOCOL_CMS;
+ }
+ const QByteArray fingerprint = QByteArray::number(count, 16).rightJustified(40, '0');
+ key->fpr = strdup(fingerprint.constData());
+ key->revoked = 0;
+ key->expired = 0;
+ key->disabled = 0;
+ key->can_encrypt = int(usage == KeyUsage::AnyUsage || usage == KeyUsage::Encrypt);
+ key->can_sign = int(usage == KeyUsage::AnyUsage || usage == KeyUsage::Sign);
+ key->secret = 1;
+ key->uids->validity = mapValidity(validity);
+
+ return GpgME::Key(key, false);
+}
+
+auto testKey(const char *address, GpgME::Protocol protocol = GpgME::UnknownProtocol)
+{
+ const auto email = GpgME::UserID::addrSpecFromString(address);
+ const auto keys = KeyCache::instance()->findByEMailAddress(email);
+ for (const auto &key: keys) {
+ if (protocol == GpgME::UnknownProtocol || key.protocol() == protocol) {
+ return key;
+ }
+ }
+ return GpgME::Key();
+}
+
+void waitForKeySelectionComboBeingInitialized(const KeySelectionCombo *combo)
+{
+ QVERIFY(combo);
+
+ const auto spy = std::make_unique<QSignalSpy>(combo, &KeySelectionCombo::keyListingFinished);
+ QVERIFY(spy->isValid());
+ QVERIFY(spy->wait(10));
+}
+
+}
+
+class KeySelectionComboTest: public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void init()
+ {
+ // hold a reference to the key cache to avoid rebuilding while the test is running
+ mKeyCache = KeyCache::instance();
+
+ KeyCache::mutableInstance()->setKeys({
+ createTestKey("sender@example.net", GpgME::OpenPGP, KeyUsage::AnyUsage),
+ createTestKey("sender@example.net", GpgME::CMS, KeyUsage::AnyUsage),
+ createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP, KeyUsage::Encrypt),
+ createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS, KeyUsage::Encrypt),
+ createTestKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP, KeyUsage::Encrypt, GpgME::UserID::Marginal),
+ });
+ }
+
+ void cleanup()
+ {
+ // verify that nobody else holds a reference to the key cache
+ QVERIFY(mKeyCache.use_count() == 1);
+ mKeyCache.reset();
+ }
+
+ void test__verify_test_keys()
+ {
+ QVERIFY(!testKey("sender@example.net", GpgME::OpenPGP).isNull());
+ QVERIFY(!testKey("sender@example.net", GpgME::CMS).isNull());
+ QVERIFY(!testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).isNull());
+ QVERIFY(!testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS).isNull());
+ QVERIFY(!testKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP).isNull());
+ }
+
+ void test__after_initialization_default_key_is_current_key()
+ {
+ const auto combo = std::make_unique<KeySelectionCombo>();
+ combo->setDefaultKey(QString::fromLatin1(testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint()));
+ waitForKeySelectionComboBeingInitialized(combo.get());
+
+ QCOMPARE(combo->currentKey().primaryFingerprint(), testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint());
+ }
+
+ void test__currently_selected_key_is_retained_if_cache_is_updated()
+ {
+ const auto combo = std::make_unique<KeySelectionCombo>();
+ combo->setDefaultKey(QString::fromLatin1(testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint()));
+ waitForKeySelectionComboBeingInitialized(combo.get());
+
+ combo->setCurrentIndex(3);
+
+ QCOMPARE(combo->currentKey().primaryFingerprint(), testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS).primaryFingerprint());
+
+ Q_EMIT KeyCache::mutableInstance()->keyListingDone(GpgME::KeyListResult{});
+
+ QCOMPARE(combo->currentKey().primaryFingerprint(), testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS).primaryFingerprint());
+ }
+
+ void test__default_key_is_selected_if_currently_selected_key_is_gone_after_model_update()
+ {
+ const auto combo = std::make_unique<KeySelectionCombo>();
+ combo->setDefaultKey(QString::fromLatin1(testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint()));
+ waitForKeySelectionComboBeingInitialized(combo.get());
+
+ combo->setCurrentIndex(3);
+
+ QCOMPARE(combo->currentKey().primaryFingerprint(), testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS).primaryFingerprint());
+
+ KeyCache::mutableInstance()->setKeys({
+ testKey("sender@example.net", GpgME::OpenPGP),
+ testKey("sender@example.net", GpgME::CMS),
+ testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP),
+ testKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP),
+ });
+
+ QCOMPARE(combo->currentKey().primaryFingerprint(), testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint());
+ }
+
+ void test__currently_selected_custom_item_is_retained_if_cache_is_updated()
+ {
+ const auto combo = std::make_unique<KeySelectionCombo>();
+ combo->prependCustomItem({}, {}, QStringLiteral("custom1"));
+ combo->appendCustomItem({}, {}, QStringLiteral("custom2"));
+ combo->setDefaultKey(QString::fromLatin1(testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint()));
+ waitForKeySelectionComboBeingInitialized(combo.get());
+
+ combo->setCurrentIndex(combo->count() - 1);
+ QCOMPARE(combo->currentData(), QStringLiteral("custom2"));
+
+ Q_EMIT KeyCache::mutableInstance()->keyListingDone(GpgME::KeyListResult{});
+
+ QCOMPARE(combo->currentData(), QStringLiteral("custom2"));
+ }
+
+ void test__default_key_is_selected_if_currently_selected_custom_item_is_gone_after_model_update()
+ {
+ const auto combo = std::make_unique<KeySelectionCombo>();
+ combo->prependCustomItem({}, {}, QStringLiteral("custom1"));
+ combo->appendCustomItem({}, {}, QStringLiteral("custom2"));
+ combo->setDefaultKey(QString::fromLatin1(testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint()));
+ waitForKeySelectionComboBeingInitialized(combo.get());
+
+ combo->setCurrentIndex(combo->count() - 1);
+ QCOMPARE(combo->currentData(), QStringLiteral("custom2"));
+
+ combo->removeCustomItem(QStringLiteral("custom2"));
+
+ QCOMPARE(combo->currentKey().primaryFingerprint(), testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).primaryFingerprint());
+ }
+
+private:
+ std::shared_ptr<const KeyCache> mKeyCache;
+};
+
+QTEST_MAIN(KeySelectionComboTest)
+#include "keyselectioncombotest.moc"
diff --git a/src/ui/keyselectioncombo.cpp b/src/ui/keyselectioncombo.cpp
index c4ef2925..f60f4d49 100644
--- a/src/ui/keyselectioncombo.cpp
+++ b/src/ui/keyselectioncombo.cpp
@@ -1,588 +1,634 @@
/* This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyselectioncombo.h"
#include <kleo_ui_debug.h>
#include "kleo/dn.h"
#include "models/keylist.h"
#include "models/keylistmodel.h"
#include "models/keylistsortfilterproxymodel.h"
#include "models/keycache.h"
#include "utils/formatting.h"
#include "progressbar.h"
#include "kleo/defaultkeyfilter.h"
#include <gpgme++/key.h>
#include <QSortFilterProxyModel>
#include <QVector>
#include <QTimer>
#include <KLocalizedString>
using namespace Kleo;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
class SortFilterProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
void setAlwaysAcceptedKey(const QString &fingerprint)
{
if (fingerprint == mFingerprint) {
return;
}
mFingerprint = fingerprint;
invalidate();
}
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
if (!mFingerprint.isEmpty()) {
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString();
if (fingerprint == mFingerprint) {
return true;
}
}
return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
private:
QString mFingerprint;
};
class ProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
private:
struct CustomItem {
QIcon icon;
QString text;
QVariant data;
QString toolTip;
};
public:
ProxyModel(QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
}
~ProxyModel() override
{
qDeleteAll(mFrontItems);
qDeleteAll(mBackItems);
}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value<GpgME::Key>();
const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value<GpgME::Key>();
if (leftKey.isNull()) {
return false;
}
if (rightKey.isNull()) {
return true;
}
// As we display UID(0) this is ok. We probably need a get Best UID at some point.
const auto lUid = leftKey.userID(0);
const auto rUid = rightKey.userID(0);
if (lUid.isNull()) {
return false;
}
if (rUid.isNull()) {
return true;
}
int cmp = strcmp (lUid.id(), rUid.id());
if (cmp) {
return cmp < 0;
}
if (lUid.validity() == rUid.validity()) {
/* Both are the same check which one is newer. */
time_t oldTime = 0;
for (const GpgME::Subkey &s: leftKey.subkeys()) {
if (s.isRevoked() || s.isInvalid() || s.isDisabled()) {
continue;
}
if (s.creationTime() > oldTime) {
oldTime= s.creationTime();
}
}
time_t newTime = 0;
for (const GpgME::Subkey &s: rightKey.subkeys()) {
if (s.isRevoked() || s.isInvalid() || s.isDisabled()) {
continue;
}
if (s.creationTime() > newTime) {
newTime = s.creationTime();
}
}
return newTime < oldTime;
}
return lUid.validity() > rUid.validity();
}
bool isCustomItem(const int row) const
{
return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount();
}
void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
beginInsertRows(QModelIndex(), 0, 0);
mFrontItems.push_front(new CustomItem{ icon, text, data, toolTip });
endInsertRows();
}
void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
mBackItems.push_back(new CustomItem{ icon, text, data, toolTip });
endInsertRows();
}
void removeCustomItem(const QVariant &data)
{
for (int i = 0; i < mFrontItems.count(); ++i) {
if (mFrontItems[i]->data == data) {
beginRemoveRows(QModelIndex(), i, i);
delete mFrontItems.takeAt(i);
endRemoveRows();
return;
}
}
for (int i = 0; i < mBackItems.count(); ++i) {
if (mBackItems[i]->data == data) {
const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i;
beginRemoveRows(QModelIndex(), index, index);
delete mBackItems.takeAt(i);
endRemoveRows();
return;
}
}
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count();
}
QModelIndex mapToSource(const QModelIndex &index) const override
{
if (!isCustomItem(index.row())) {
const int row = index.row() - mFrontItems.count();
return sourceModel()->index(row, index.column());
} else {
return {};
}
}
QModelIndex mapFromSource(const QModelIndex &source_index) const override
{
const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer());
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (row < 0 || row >= rowCount()) {
return {};
}
if (row < mFrontItems.count()) {
return createIndex(row, column, mFrontItems[row]);
} else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) {
return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]);
} else {
const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent);
return createIndex(row, column, mi.internalPointer());
}
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
Q_UNUSED(index)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
}
QModelIndex parent(const QModelIndex &) const override
{
// Flat list
return {};
}
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return QVariant();
}
if (isCustomItem(index.row())) {
Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
auto ci = static_cast<CustomItem*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return ci->text;
case Qt::DecorationRole:
return ci->icon;
case Qt::UserRole:
return ci->data;
case Qt::ToolTipRole:
return ci->toolTip;
default:
return QVariant();
}
}
const auto key = QSortFilterProxyModel::data(index, KeyList::KeyRole).value<GpgME::Key>();
Q_ASSERT(!key.isNull());
if (key.isNull()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole: {
const auto userID = key.userID(0);
QString name, email;
if (key.protocol() == GpgME::OpenPGP) {
name = QString::fromUtf8(userID.name());
email = QString::fromUtf8(userID.email());
} else {
const Kleo::DN dn(userID.id());
name = dn[QStringLiteral("CN")];
email = dn[QStringLiteral("EMAIL")];
}
return i18nc("Name <email> (validity, type, created: date)", "%1 (%2, %3 created: %4)",
email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name <email>", "%1 <%2>", name, email),
Kleo::Formatting::complianceStringShort(key),
Kleo::KeyCache::instance()->pgpOnly() ? QString() :
key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP") + QLatin1Char(',') : i18n("S/MIME") + QLatin1Char(','),
Kleo::Formatting::creationDateString(key));
}
case Qt::ToolTipRole:
return Kleo::Formatting::toolTip(key, Kleo::Formatting::Validity |
Kleo::Formatting::Issuer |
Kleo::Formatting::Subject |
Kleo::Formatting::Fingerprint |
Kleo::Formatting::ExpiryDates |
Kleo::Formatting::UserIDs);
case Qt::DecorationRole:
return Kleo::Formatting::iconForUid(key.userID(0));
default:
return QSortFilterProxyModel::data(index, role);
}
}
private:
QVector<CustomItem*> mFrontItems;
QVector<CustomItem*> mBackItems;
};
} // anonymous namespace
namespace Kleo
{
class KeySelectionComboPrivate
{
public:
KeySelectionComboPrivate(KeySelectionCombo *parent)
: wasEnabled(true)
, q(parent)
{
}
/* Selects the first key with a UID addrSpec that matches
* the mPerfectMatchMbox variable.
*
* The idea here is that if there are keys like:
*
* tom-store@abc.com
* susi-store@abc.com
* store@abc.com
*
* And the user wants to send a mail to "store@abc.com"
* the filter should still show tom and susi (because they
* both are part of store) but the key for "store" should
* be preselected.
*
* Returns true if one was selected. False otherwise. */
bool selectPerfectIdMatch() const
{
if (mPerfectMatchMbox.isEmpty()) {
return false;
}
for (int i = 0; i < proxyModel->rowCount(); ++i) {
const auto idx = proxyModel->index(i, 0, QModelIndex());
const auto key = proxyModel->data(idx, KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
// WTF?
continue;
}
for (const auto &uid: key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()) == mPerfectMatchMbox) {
q->setCurrentIndex(i);
return true;
}
}
}
return false;
}
/* Updates the current key with the default key if the key matches
* the current key filter. */
void updateWithDefaultKey() {
GpgME::Protocol filterProto = GpgME::UnknownProtocol;
const auto filter = dynamic_cast<const DefaultKeyFilter*> (sortFilterProxy->keyFilter().get());
if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) {
filterProto = GpgME::OpenPGP;
} else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
filterProto = GpgME::CMS;
}
QString defaultKey = defaultKeys.value (filterProto);
if (defaultKey.isEmpty()) {
// Fallback to unknown protocol
defaultKey = defaultKeys.value (GpgME::UnknownProtocol);
}
// make sure that the default key is not filtered out unless it has the wrong protocol
if (filterProto == GpgME::UnknownProtocol) {
sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
} else {
const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData());
if (!key.isNull() && key.protocol() == filterProto) {
sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
} else {
sortFilterProxy->setAlwaysAcceptedKey({});
}
}
q->setCurrentKey(defaultKey);
}
+ void storeCurrentSelectionBeforeModelChange()
+ {
+ keyBeforeModelChange = q->currentKey();
+ customItemBeforeModelChange = q->currentData();
+ }
+
+ void restoreCurrentSelectionAfterModelChange()
+ {
+ if (!keyBeforeModelChange.isNull()) {
+ q->setCurrentKey(keyBeforeModelChange);
+ } else if (customItemBeforeModelChange.isValid()) {
+ const auto index = q->findData(customItemBeforeModelChange);
+ if (index != -1) {
+ q->setCurrentIndex(index);
+ } else {
+ updateWithDefaultKey();
+ }
+ }
+ }
+
Kleo::AbstractKeyListModel *model = nullptr;
SortFilterProxyModel *sortFilterProxy = nullptr;
ProxyModel *proxyModel = nullptr;
std::shared_ptr<Kleo::KeyCache> cache;
QMap<GpgME::Protocol, QString> defaultKeys;
bool wasEnabled = false;
bool useWasEnabled = false;
- bool secretOnly;
+ bool secretOnly = false;
+ bool initialKeyListingDone = false;
QString mPerfectMatchMbox;
+ GpgME::Key keyBeforeModelChange;
+ QVariant customItemBeforeModelChange;
private:
KeySelectionCombo * const q;
};
}
using namespace Kleo;
KeySelectionCombo::KeySelectionCombo(QWidget* parent)
: KeySelectionCombo(true, parent)
{}
KeySelectionCombo::KeySelectionCombo(bool secretOnly, QWidget* parent)
: QComboBox(parent)
, d(new KeySelectionComboPrivate(this))
{
d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this);
d->secretOnly = secretOnly;
d->sortFilterProxy = new SortFilterProxyModel(this);
d->sortFilterProxy->setSourceModel(d->model);
d->proxyModel = new ProxyModel(this);
d->proxyModel->setSourceModel(d->sortFilterProxy);
setModel(d->proxyModel);
connect(this, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [this](int row) {
if (row >= 0 && row < d->proxyModel->rowCount()) {
if (d->proxyModel->isCustomItem(row)) {
- Q_EMIT customItemSelected(d->proxyModel->index(row, 0).data(Qt::UserRole));
+ Q_EMIT customItemSelected(currentData(Qt::UserRole));
} else {
Q_EMIT currentKeyChanged(currentKey());
}
}
});
d->cache = Kleo::KeyCache::mutableInstance();
+ connect(model(), &QAbstractItemModel::rowsAboutToBeInserted,
+ this, [this] () { d->storeCurrentSelectionBeforeModelChange(); });
+ connect(model(), &QAbstractItemModel::rowsInserted,
+ this, [this] () { d->restoreCurrentSelectionAfterModelChange(); });
+ connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved,
+ this, [this] () { d->storeCurrentSelectionBeforeModelChange(); });
+ connect(model(), &QAbstractItemModel::rowsRemoved,
+ this, [this] () { d->restoreCurrentSelectionAfterModelChange(); });
+ connect(model(), &QAbstractItemModel::modelAboutToBeReset,
+ this, [this] () { d->storeCurrentSelectionBeforeModelChange(); });
+ connect(model(), &QAbstractItemModel::modelReset,
+ this, [this] () { d->restoreCurrentSelectionAfterModelChange(); });
+
QTimer::singleShot(0, this, &KeySelectionCombo::init);
}
KeySelectionCombo::~KeySelectionCombo()
{
delete d;
}
void KeySelectionCombo::init()
{
connect(d->cache.get(), &Kleo::KeyCache::keyListingDone,
this, [this]() {
// Set useKeyCache ensures that the cache is populated
// so this can be a blocking call if the cache is not initialized
- d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
- d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys"));
+ if (!d->initialKeyListingDone) {
+ d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
+ d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys"));
+ }
// We use the useWasEnabled state variable to decide if we should
// change the enable / disable state based on the keylist done signal.
// If we triggered the refresh useWasEnabled is true and we want to
// enable / disable again after our refresh, as the refresh disabled it.
//
// But if a keyListingDone signal comes from just a generic refresh
// triggered by someone else we don't want to change the enable / disable
// state.
if (d->useWasEnabled) {
setEnabled(d->wasEnabled);
d->useWasEnabled = false;
}
Q_EMIT keyListingFinished();
});
connect(this, &KeySelectionCombo::keyListingFinished, this, [this]() {
- d->updateWithDefaultKey();
+ if (!d->initialKeyListingDone) {
+ d->updateWithDefaultKey();
+ d->initialKeyListingDone = true;
+ }
});
if (!d->cache->initialized()) {
refreshKeys();
} else {
d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
Q_EMIT keyListingFinished();
}
connect(this, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] () {
setToolTip(currentData(Qt::ToolTipRole).toString());
});
}
void KeySelectionCombo::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
{
d->sortFilterProxy->setKeyFilter(kf);
d->proxyModel->sort(0);
d->updateWithDefaultKey();
}
std::shared_ptr<const KeyFilter> KeySelectionCombo::keyFilter() const
{
return d->sortFilterProxy->keyFilter();
}
void KeySelectionCombo::setIdFilter(const QString &id)
{
d->sortFilterProxy->setFilterRegExp(id);
d->mPerfectMatchMbox = id;
d->updateWithDefaultKey();
}
QString KeySelectionCombo::idFilter() const
{
return d->sortFilterProxy->filterRegExp().pattern();
}
GpgME::Key Kleo::KeySelectionCombo::currentKey() const
{
return currentData(KeyList::KeyRole).value<GpgME::Key>();
}
void Kleo::KeySelectionCombo::setCurrentKey(const GpgME::Key &key)
{
- const int idx = findData(QVariant::fromValue(key), KeyList::KeyRole, Qt::MatchExactly);
+ const int idx = findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly);
if (idx > -1) {
setCurrentIndex(idx);
- } else {
- d->selectPerfectIdMatch();
+ } else if (!d->selectPerfectIdMatch()) {
+ d->updateWithDefaultKey();
}
setToolTip(currentData(Qt::ToolTipRole).toString());
}
void Kleo::KeySelectionCombo::setCurrentKey(const QString &fingerprint)
{
const auto cur = currentKey();
if (!cur.isNull() && !fingerprint.isEmpty() &&
fingerprint == QLatin1String(cur.primaryFingerprint())) {
// already set; still emit a changed signal because the current key may
// have become the item at the current index by changes in the underlying model
Q_EMIT currentKeyChanged(cur);
return;
}
const int idx = findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly);
if (idx > -1) {
setCurrentIndex(idx);
} else if (!d->selectPerfectIdMatch()) {
setCurrentIndex(0);
}
setToolTip(currentData(Qt::ToolTipRole).toString());
}
void KeySelectionCombo::refreshKeys()
{
d->wasEnabled = isEnabled();
d->useWasEnabled = true;
setEnabled(false);
const bool wasBlocked = blockSignals(true);
prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys"));
setCurrentIndex(0);
blockSignals(wasBlocked);
d->cache->startKeyListing();
}
void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
d->proxyModel->appendItem(icon, text, data, toolTip);
}
void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
{
appendCustomItem(icon, text, data, QString());
}
void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
d->proxyModel->prependItem(icon, text, data, toolTip);
}
void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
{
prependCustomItem(icon, text, data, QString());
}
+void KeySelectionCombo::removeCustomItem(const QVariant &data)
+{
+ d->proxyModel->removeCustomItem(data);
+}
+
void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto)
{
d->defaultKeys.insert(proto, fingerprint);
d->updateWithDefaultKey();
}
void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint)
{
setDefaultKey(fingerprint, GpgME::UnknownProtocol);
}
QString Kleo::KeySelectionCombo::defaultKey(GpgME::Protocol proto) const
{
return d->defaultKeys.value(proto);
}
QString Kleo::KeySelectionCombo::defaultKey() const
{
return defaultKey(GpgME::UnknownProtocol);
}
#include "keyselectioncombo.moc"
diff --git a/src/ui/keyselectioncombo.h b/src/ui/keyselectioncombo.h
index 43b4410f..3b5746e1 100644
--- a/src/ui/keyselectioncombo.h
+++ b/src/ui/keyselectioncombo.h
@@ -1,71 +1,72 @@
/* This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QComboBox>
#include <gpgme++/global.h>
#include <kleo_export.h>
#include <libkleo/enum.h>
#include <memory>
namespace GpgME
{
class Key;
}
namespace Kleo
{
class KeyFilter;
class KeySelectionComboPrivate;
class KLEO_EXPORT KeySelectionCombo : public QComboBox
{
Q_OBJECT
public:
explicit KeySelectionCombo(QWidget *parent = nullptr);
explicit KeySelectionCombo(bool secretOnly, QWidget *parent = nullptr);
~KeySelectionCombo() override;
void setKeyFilter(const std::shared_ptr<const KeyFilter> &kf);
std::shared_ptr<const KeyFilter> keyFilter() const;
void setIdFilter(const QString &id);
QString idFilter() const;
void refreshKeys();
GpgME::Key currentKey() const;
void setCurrentKey(const GpgME::Key &key);
void setCurrentKey(const QString &fingerprint);
void setDefaultKey(const QString &fingerprint);
void setDefaultKey(const QString &fingerprint, GpgME::Protocol proto);
QString defaultKey() const;
QString defaultKey(GpgME::Protocol proto) const;
void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data);
void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data);
void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip);
void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip);
+ void removeCustomItem(const QVariant &data);
Q_SIGNALS:
void customItemSelected(const QVariant &data);
void currentKeyChanged(const GpgME::Key &key);
void keyListingFinished();
protected:
virtual void init();
private:
KeySelectionComboPrivate * const d;
};
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 7:03 PM (2 h, 36 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7d/53/064337c98b628a1f5e4b939c3401

Event Timeline