7#include <config-libkleo.h>
9#include "keyselectioncombo.h"
11#include "progressbar.h"
13#include <libkleo/defaultkeyfilter.h>
14#include <libkleo/dn.h>
15#include <libkleo/formatting.h>
16#include <libkleo/keycache.h>
17#include <libkleo/keylist.h>
18#include <libkleo/keylistmodel.h>
19#include <libkleo/keylistsortfilterproxymodel.h>
21#include <kleo_ui_debug.h>
23#include <KLocalizedString>
26#include <QSortFilterProxyModel>
29#include <gpgme++/key.h>
34Q_DECLARE_METATYPE(GpgME::Key)
38class SortFilterProxyModel :
public KeyListSortFilterProxyModel
43 using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
45 void setAlwaysAcceptedKey(
const QString &fingerprint)
47 if (fingerprint == mFingerprint) {
50 mFingerprint = fingerprint;
55 bool filterAcceptsRow(
int source_row,
const QModelIndex &source_parent)
const override
57 if (!mFingerprint.isEmpty()) {
58 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
59 const auto fingerprint = sourceModel()->
data(index, KeyList::FingerprintRole).toString();
60 if (fingerprint == mFingerprint) {
65 return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
72static QString formatUserID(
const GpgME::Key &key)
74 const auto userID = key.userID(0);
78 if (key.protocol() == GpgME::OpenPGP) {
83 name = dn[QStringLiteral(
"CN")];
84 email = dn[QStringLiteral(
"EMAIL")];
86 return email.isEmpty() ? name : name.isEmpty() ? email :
i18nc(
"Name <email>",
"%1 <%2>", name, email);
96 , mIconProvider{usageFlags}
103 const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value<GpgME::Key>();
104 const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value<GpgME::Key>();
105 if (leftKey.isNull()) {
108 if (rightKey.isNull()) {
112 const auto lUid = leftKey.userID(0);
113 const auto rUid = rightKey.userID(0);
120 const auto leftNameAndEmail = formatUserID(leftKey);
121 const auto rightNameAndEmail = formatUserID(rightKey);
127 if (lUid.validity() != rUid.validity()) {
128 return lUid.validity() > rUid.validity();
133 for (
const GpgME::Subkey &s : leftKey.subkeys()) {
137 if (s.creationTime() > leftTime) {
138 leftTime = s.creationTime();
141 time_t rightTime = 0;
142 for (
const GpgME::Subkey &s : rightKey.subkeys()) {
146 if (s.creationTime() > rightTime) {
147 rightTime = s.creationTime();
150 if (rightTime != leftTime) {
151 return leftTime > rightTime;
155 return strcmp(leftKey.primaryFingerprint(), rightKey.primaryFingerprint()) < 0;
166 Q_ASSERT(!key.isNull());
174 const auto nameAndEmail = formatUserID(key);
175 if (Kleo::KeyCache::instance()->pgpOnly()) {
176 return i18nc(
"Name <email> (validity, created: date)",
177 "%1 (%2, created: %3)",
179 Kleo::Formatting::complianceStringShort(key),
180 Kleo::Formatting::creationDateString(key));
182 return i18nc(
"Name <email> (validity, type, created: date)",
183 "%1 (%2, %3, created: %4)",
185 Kleo::Formatting::complianceStringShort(key),
186 Formatting::displayName(key.protocol()),
187 Kleo::Formatting::creationDateString(key));
191 using namespace Kleo::Formatting;
192 return Kleo::Formatting::toolTip(key, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
195 return mIconProvider.icon(key);
203 Formatting::IconProvider mIconProvider;
219 CustomItemsProxyModel(
QObject *parent =
nullptr)
224 ~CustomItemsProxyModel()
override
226 qDeleteAll(mFrontItems);
227 qDeleteAll(mBackItems);
230 bool isCustomItem(
const int row)
const
238 mFrontItems.push_front(
new CustomItem{icon, text, data, toolTip});
244 beginInsertRows(
QModelIndex(), rowCount(), rowCount());
245 mBackItems.push_back(
new CustomItem{icon, text, data, toolTip});
249 void removeCustomItem(
const QVariant &data)
251 for (
int i = 0; i < mFrontItems.count(); ++i) {
252 if (mFrontItems[i]->data == data) {
254 delete mFrontItems.takeAt(i);
259 for (
int i = 0; i < mBackItems.count(); ++i) {
260 if (mBackItems[i]->data == data) {
263 delete mBackItems.takeAt(i);
289 if (!isCustomItem(index.
row())) {
290 const int sourceRow = index.
row() - mFrontItems.count();
304 if (row < 0 || row >= rowCount()) {
307 if (row < mFrontItems.count()) {
308 return createIndex(row, column, mFrontItems[row]);
335 if (isCustomItem(index.
row())) {
336 Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
364class KeySelectionComboPrivate
367 KeySelectionComboPrivate(KeySelectionCombo *parent,
bool secretOnly_,
KeyUsage::Flags usage)
369 , secretOnly{secretOnly_}
390 bool selectPerfectIdMatch()
const
392 if (mPerfectMatchMbox.
isEmpty()) {
396 for (
int i = 0; i < proxyModel->rowCount(); ++i) {
397 const auto idx = proxyModel->index(i, 0,
QModelIndex());
398 const auto key = proxyModel->data(idx, KeyList::KeyRole).value<GpgME::Key>();
403 for (
const auto &uid : key.userIDs()) {
415 void updateWithDefaultKey()
417 GpgME::Protocol filterProto = GpgME::UnknownProtocol;
420 if (filter &&
filter->isOpenPGP() == DefaultKeyFilter::Set) {
421 filterProto = GpgME::OpenPGP;
422 }
else if (filter &&
filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
423 filterProto = GpgME::CMS;
429 defaultKey = defaultKeys.
value(GpgME::UnknownProtocol);
432 if (filterProto == GpgME::UnknownProtocol) {
433 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
435 const auto key = KeyCache::instance()->findByFingerprint(defaultKey.
toLatin1().
constData());
436 if (!key.isNull() && key.protocol() == filterProto) {
437 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
439 sortFilterProxy->setAlwaysAcceptedKey({});
442 q->setCurrentKey(defaultKey);
445 void storeCurrentSelectionBeforeModelChange()
447 keyBeforeModelChange = q->currentKey();
451 void restoreCurrentSelectionAfterModelChange()
453 if (!keyBeforeModelChange.isNull()) {
454 q->setCurrentKey(keyBeforeModelChange);
455 }
else if (customItemBeforeModelChange.
isValid()) {
456 const auto index = q->
findData(customItemBeforeModelChange);
460 updateWithDefaultKey();
465 Kleo::AbstractKeyListModel *model =
nullptr;
466 SortFilterProxyModel *sortFilterProxy =
nullptr;
467 SortAndFormatCertificatesProxyModel *sortAndFormatProxy =
nullptr;
468 CustomItemsProxyModel *proxyModel =
nullptr;
469 std::shared_ptr<Kleo::KeyCache> cache;
471 bool wasEnabled =
false;
472 bool useWasEnabled =
false;
473 bool secretOnly =
false;
474 bool initialKeyListingDone =
false;
476 GpgME::Key keyBeforeModelChange;
477 QVariant customItemBeforeModelChange;
481 KeySelectionCombo *
const q;
488KeySelectionCombo::KeySelectionCombo(
QWidget *parent)
489 : KeySelectionCombo(true, KeyUsage::
None, parent)
493KeySelectionCombo::KeySelectionCombo(
bool secretOnly,
QWidget *parent)
494 : KeySelectionCombo(secretOnly, KeyUsage::
None, parent)
499 : KeySelectionCombo{false, usage, parent}
503KeySelectionCombo::KeySelectionCombo(KeyUsage::Flag usage,
QWidget *parent)
504 : KeySelectionCombo{false, usage, parent}
510 , d(new KeySelectionComboPrivate(this, secretOnly, usage))
514 setAccessibleDescription(QStringLiteral(
" "));
515 d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(
this);
517 d->sortFilterProxy =
new SortFilterProxyModel(
this);
518 d->sortFilterProxy->setSourceModel(d->model);
520 d->sortAndFormatProxy =
new SortAndFormatCertificatesProxyModel{usage,
this};
521 d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
523 d->sortAndFormatProxy->sort(0);
525 d->proxyModel =
new CustomItemsProxyModel{
this};
526 d->proxyModel->setSourceModel(d->sortAndFormatProxy);
528 setModel(d->proxyModel);
530 if (row >= 0 && row < d->proxyModel->rowCount()) {
531 if (d->proxyModel->isCustomItem(row)) {
532 Q_EMIT customItemSelected(currentData(Qt::UserRole));
534 Q_EMIT currentKeyChanged(currentKey());
539 d->cache = Kleo::KeyCache::mutableInstance();
542 d->storeCurrentSelectionBeforeModelChange();
545 d->restoreCurrentSelectionAfterModelChange();
548 d->storeCurrentSelectionBeforeModelChange();
551 d->restoreCurrentSelectionAfterModelChange();
554 d->storeCurrentSelectionBeforeModelChange();
557 d->restoreCurrentSelectionAfterModelChange();
563KeySelectionCombo::~KeySelectionCombo() =
default;
565void KeySelectionCombo::init()
567 connect(d->cache.get(), &Kleo::KeyCache::keyListingDone,
this, [
this]() {
570 if (!d->initialKeyListingDone) {
571 d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
573 d->proxyModel->removeCustomItem(QStringLiteral(
"-libkleo-loading-keys"));
583 if (d->useWasEnabled) {
584 setEnabled(d->wasEnabled);
585 d->useWasEnabled = false;
587 Q_EMIT keyListingFinished();
590 connect(
this, &KeySelectionCombo::keyListingFinished,
this, [
this]() {
591 if (!d->initialKeyListingDone) {
592 d->updateWithDefaultKey();
593 d->initialKeyListingDone = true;
597 if (!d->cache->initialized()) {
600 d->model->useKeyCache(
true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
601 Q_EMIT keyListingFinished();
609void KeySelectionCombo::setKeyFilter(
const std::shared_ptr<const KeyFilter> &kf)
611 d->sortFilterProxy->setKeyFilter(kf);
612 d->updateWithDefaultKey();
615std::shared_ptr<const KeyFilter> KeySelectionCombo::keyFilter()
const
617 return d->sortFilterProxy->keyFilter();
620void KeySelectionCombo::setIdFilter(
const QString &
id)
622 d->sortFilterProxy->setFilterRegularExpression(
id);
623 d->mPerfectMatchMbox = id;
624 d->updateWithDefaultKey();
627QString KeySelectionCombo::idFilter()
const
629 return d->sortFilterProxy->filterRegularExpression().pattern();
632GpgME::Key Kleo::KeySelectionCombo::currentKey()
const
634 return currentData(KeyList::KeyRole).value<GpgME::Key>();
637void Kleo::KeySelectionCombo::setCurrentKey(
const GpgME::Key &key)
641 setCurrentIndex(idx);
642 }
else if (!d->selectPerfectIdMatch()) {
643 d->updateWithDefaultKey();
648void Kleo::KeySelectionCombo::setCurrentKey(
const QString &fingerprint)
650 const auto cur = currentKey();
654 Q_EMIT currentKeyChanged(cur);
657 const int idx = findData(fingerprint, KeyList::FingerprintRole,
Qt::MatchExactly);
659 setCurrentIndex(idx);
660 }
else if (!d->selectPerfectIdMatch()) {
666void KeySelectionCombo::refreshKeys()
669 d->useWasEnabled =
true;
672 prependCustomItem(
QIcon(),
i18n(
"Loading keys ..."), QStringLiteral(
"-libkleo-loading-keys"));
675 d->cache->startKeyListing();
680 d->proxyModel->appendItem(icon, text, data,
toolTip);
683void KeySelectionCombo::appendCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
685 appendCustomItem(icon, text, data,
QString());
690 d->proxyModel->prependItem(icon, text, data,
toolTip);
693void KeySelectionCombo::prependCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
695 prependCustomItem(icon, text, data,
QString());
698void KeySelectionCombo::removeCustomItem(
const QVariant &data)
700 d->proxyModel->removeCustomItem(data);
703void Kleo::KeySelectionCombo::setDefaultKey(
const QString &fingerprint, GpgME::Protocol proto)
705 d->defaultKeys.insert(proto, fingerprint);
706 d->updateWithDefaultKey();
709void Kleo::KeySelectionCombo::setDefaultKey(
const QString &fingerprint)
711 setDefaultKey(fingerprint, GpgME::UnknownProtocol);
714QString Kleo::KeySelectionCombo::defaultKey(GpgME::Protocol proto)
const
716 return d->defaultKeys.value(proto);
719QString Kleo::KeySelectionCombo::defaultKey()
const
721 return defaultKey(GpgME::UnknownProtocol);
723#include "keyselectioncombo.moc"
725#include "moc_keyselectioncombo.cpp"
Default implementation of key filter class.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
void modelAboutToBeReset()
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
const char * constData() const const
void setCurrentIndex(int index)
void currentIndexChanged(int index)
int findData(const QVariant &data, int role, Qt::MatchFlags flags) const const
T value(const Key &key, const T &defaultValue) const const
void * internalPointer() const const
bool isValid() const const
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual QVariant data(const QModelIndex &index, int role) const const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual int rowCount(const QModelIndex &parent) const const override
QString fromLatin1(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
int localeAwareCompare(QStringView s1, QStringView s2)
QByteArray toLatin1() const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isValid() const const