7#include <config-libkleo.h>
9#include "useridselectioncombo.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>
20#include <libkleo/useridproxymodel.h>
22#include <kleo_ui_debug.h>
24#include <KLocalizedString>
27#include <QSortFilterProxyModel>
30#include <gpgme++/key.h>
35Q_DECLARE_METATYPE(GpgME::Key)
39class SortFilterProxyModel :
public KeyListSortFilterProxyModel
44 using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
46 void setAlwaysAcceptedKey(
const QString &fingerprint)
48 if (fingerprint == mFingerprint) {
51 mFingerprint = fingerprint;
56 bool filterAcceptsRow(
int source_row,
const QModelIndex &source_parent)
const override
58 if (!mFingerprint.isEmpty()) {
59 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
60 const auto fingerprint = sourceModel()->
data(index, KeyList::FingerprintRole).toString();
61 if (fingerprint == mFingerprint) {
66 return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
73static QString formatUserID(
const GpgME::UserID &userID)
78 if (userID.parent().protocol() == GpgME::OpenPGP) {
83 name = dn[QStringLiteral(
"CN")];
84 email = dn[QStringLiteral(
"EMAIL")];
86 name =
Kleo::DN(userID.parent().userID(0).id())[QStringLiteral(
"CN")];
89 return email.
isEmpty() ? name : name.isEmpty() ? email :
i18nc(
"Name <email>",
"%1 <%2>", name, email);
99 , mIconProvider{usageFlags}
106 const auto leftUserId = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
107 const auto rightUserId = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
108 if (leftUserId.isNull()) {
111 if (rightUserId.isNull()) {
114 const auto leftNameAndEmail = formatUserID(leftUserId);
115 const auto rightNameAndEmail = formatUserID(rightUserId);
121 if (leftUserId.validity() != rightUserId.validity()) {
122 return leftUserId.validity() > rightUserId.validity();
127 for (
const GpgME::Subkey &s : leftUserId.parent().subkeys()) {
131 if (s.creationTime() > leftTime) {
132 leftTime = s.creationTime();
135 time_t rightTime = 0;
136 for (
const GpgME::Subkey &s : rightUserId.parent().subkeys()) {
140 if (s.creationTime() > rightTime) {
141 rightTime = s.creationTime();
144 if (rightTime != leftTime) {
145 return leftTime > rightTime;
149 return strcmp(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
160 Q_ASSERT(!userId.isNull());
161 if (userId.isNull()) {
168 const auto nameAndEmail = formatUserID(userId);
169 if (Kleo::KeyCache::instance()->pgpOnly()) {
170 return i18nc(
"Name <email> (validity, created: date)",
171 "%1 (%2, created: %3)",
173 Kleo::Formatting::complianceStringShort(userId),
174 Kleo::Formatting::creationDateString(userId.parent()));
176 return i18nc(
"Name <email> (validity, type, created: date)",
177 "%1 (%2, %3, created: %4)",
179 Kleo::Formatting::complianceStringShort(userId),
180 Formatting::displayName(userId.parent().protocol()),
181 Kleo::Formatting::creationDateString(userId.parent()));
185 using namespace Kleo::Formatting;
186 return Kleo::Formatting::toolTip(userId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
189 return mIconProvider.icon(userId.parent());
197 Formatting::IconProvider mIconProvider;
213 CustomItemsProxyModel(
QObject *parent =
nullptr)
218 ~CustomItemsProxyModel()
override
220 qDeleteAll(mFrontItems);
221 qDeleteAll(mBackItems);
224 bool isCustomItem(
const int row)
const
232 mFrontItems.push_front(
new CustomItem{icon, text, data, toolTip});
238 beginInsertRows(
QModelIndex(), rowCount(), rowCount());
239 mBackItems.push_back(
new CustomItem{icon, text, data, toolTip});
243 void removeCustomItem(
const QVariant &data)
245 for (
int i = 0; i < mFrontItems.count(); ++i) {
246 if (mFrontItems[i]->data == data) {
248 delete mFrontItems.takeAt(i);
253 for (
int i = 0; i < mBackItems.count(); ++i) {
254 if (mBackItems[i]->data == data) {
257 delete mBackItems.takeAt(i);
283 if (!isCustomItem(index.
row())) {
284 const int sourceRow = index.
row() - mFrontItems.count();
298 if (row < 0 || row >= rowCount()) {
301 if (row < mFrontItems.count()) {
302 return createIndex(row, column, mFrontItems[row]);
329 if (isCustomItem(index.
row())) {
330 Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
358class UserIDSelectionComboPrivate
361 UserIDSelectionComboPrivate(UserIDSelectionCombo *parent,
bool secretOnly_,
KeyUsage::Flags usage)
363 , secretOnly{secretOnly_}
384 bool selectPerfectIdMatch()
const
386 if (mPerfectMatchMbox.
isEmpty()) {
390 for (
int i = 0; i < proxyModel->rowCount(); ++i) {
391 const auto idx = proxyModel->index(i, 0,
QModelIndex());
392 const auto userID = idx.data(KeyList::UserIDRole).value<GpgME::UserID>();
393 if (userID.isNull()) {
407 void updateWithDefaultKey()
409 GpgME::Protocol filterProto = GpgME::UnknownProtocol;
412 if (filter &&
filter->isOpenPGP() == DefaultKeyFilter::Set) {
413 filterProto = GpgME::OpenPGP;
414 }
else if (filter &&
filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
415 filterProto = GpgME::CMS;
421 defaultKey = defaultKeys.
value(GpgME::UnknownProtocol);
424 if (filterProto == GpgME::UnknownProtocol) {
425 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
427 const auto key = KeyCache::instance()->findByFingerprint(defaultKey.
toLatin1().
constData());
428 if (!key.isNull() && key.protocol() == filterProto) {
429 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
431 sortFilterProxy->setAlwaysAcceptedKey({});
434 q->setCurrentKey(defaultKey);
437 void storeCurrentSelectionBeforeModelChange()
439 userIDBeforeModelChange = q->currentUserID();
443 void restoreCurrentSelectionAfterModelChange()
445 if (!userIDBeforeModelChange.isNull()) {
446 q->setCurrentUserID(userIDBeforeModelChange);
447 }
else if (customItemBeforeModelChange.
isValid()) {
448 const auto index = q->
findData(customItemBeforeModelChange);
452 updateWithDefaultKey();
457 Kleo::AbstractKeyListModel *model =
nullptr;
458 UserIDProxyModel *userIdProxy =
nullptr;
459 SortFilterProxyModel *sortFilterProxy =
nullptr;
460 SortAndFormatCertificatesProxyModel *sortAndFormatProxy =
nullptr;
461 CustomItemsProxyModel *proxyModel =
nullptr;
462 std::shared_ptr<Kleo::KeyCache> cache;
464 bool wasEnabled =
false;
465 bool useWasEnabled =
false;
466 bool secretOnly =
false;
467 bool initialKeyListingDone =
false;
469 GpgME::UserID userIDBeforeModelChange;
470 QVariant customItemBeforeModelChange;
474 UserIDSelectionCombo *
const q;
481UserIDSelectionCombo::UserIDSelectionCombo(
QWidget *parent)
482 : UserIDSelectionCombo(true, KeyUsage::
None, parent)
486UserIDSelectionCombo::UserIDSelectionCombo(
bool secretOnly,
QWidget *parent)
487 : UserIDSelectionCombo(secretOnly, KeyUsage::
None, parent)
492 : UserIDSelectionCombo{false, usage, parent}
496UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flag usage,
QWidget *parent)
497 : UserIDSelectionCombo{false, usage, parent}
503 , d(new UserIDSelectionComboPrivate(this, secretOnly, usage))
507 setAccessibleDescription(QStringLiteral(
" "));
508 d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(
this);
510 d->userIdProxy =
new UserIDProxyModel(
this);
511 d->userIdProxy->setSourceModel(d->model);
513 d->sortFilterProxy =
new SortFilterProxyModel(
this);
514 d->sortFilterProxy->setSourceModel(d->userIdProxy);
516 d->sortAndFormatProxy =
new SortAndFormatCertificatesProxyModel{usage,
this};
517 d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
519 d->sortAndFormatProxy->sort(0);
521 d->proxyModel =
new CustomItemsProxyModel{
this};
522 d->proxyModel->setSourceModel(d->sortAndFormatProxy);
524 setModel(d->proxyModel);
526 if (row >= 0 && row < d->proxyModel->rowCount()) {
527 if (d->proxyModel->isCustomItem(row)) {
528 Q_EMIT customItemSelected(currentData(Qt::UserRole));
530 Q_EMIT currentKeyChanged(currentKey());
535 d->cache = Kleo::KeyCache::mutableInstance();
538 d->storeCurrentSelectionBeforeModelChange();
541 d->restoreCurrentSelectionAfterModelChange();
544 d->storeCurrentSelectionBeforeModelChange();
547 d->restoreCurrentSelectionAfterModelChange();
550 d->storeCurrentSelectionBeforeModelChange();
553 d->restoreCurrentSelectionAfterModelChange();
559UserIDSelectionCombo::~UserIDSelectionCombo() =
default;
561void UserIDSelectionCombo::init()
563 connect(d->cache.get(), &Kleo::KeyCache::keyListingDone,
this, [
this]() {
566 if (!d->initialKeyListingDone) {
567 d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
569 d->proxyModel->removeCustomItem(QStringLiteral(
"-libkleo-loading-keys"));
579 if (d->useWasEnabled) {
580 setEnabled(d->wasEnabled);
581 d->useWasEnabled = false;
583 Q_EMIT keyListingFinished();
586 connect(
this, &UserIDSelectionCombo::keyListingFinished,
this, [
this]() {
587 if (!d->initialKeyListingDone) {
588 d->updateWithDefaultKey();
589 d->initialKeyListingDone = true;
593 if (!d->cache->initialized()) {
596 d->model->useKeyCache(
true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
597 Q_EMIT keyListingFinished();
605void UserIDSelectionCombo::setKeyFilter(
const std::shared_ptr<const KeyFilter> &kf)
607 d->sortFilterProxy->setKeyFilter(kf);
608 d->updateWithDefaultKey();
611std::shared_ptr<const KeyFilter> UserIDSelectionCombo::keyFilter()
const
613 return d->sortFilterProxy->keyFilter();
616void UserIDSelectionCombo::setIdFilter(
const QString &
id)
618 d->sortFilterProxy->setFilterRegularExpression(
id);
619 d->mPerfectMatchMbox = id;
620 d->updateWithDefaultKey();
623QString UserIDSelectionCombo::idFilter()
const
625 return d->sortFilterProxy->filterRegularExpression().pattern();
628GpgME::Key Kleo::UserIDSelectionCombo::currentKey()
const
630 return currentData(KeyList::KeyRole).value<GpgME::Key>();
633void Kleo::UserIDSelectionCombo::setCurrentKey(
const GpgME::Key &key)
637 setCurrentIndex(idx);
638 }
else if (!d->selectPerfectIdMatch()) {
639 d->updateWithDefaultKey();
644void Kleo::UserIDSelectionCombo::setCurrentKey(
const QString &fingerprint)
646 const auto cur = currentKey();
650 Q_EMIT currentKeyChanged(cur);
653 const int idx = findData(fingerprint, KeyList::FingerprintRole,
Qt::MatchExactly);
655 setCurrentIndex(idx);
656 }
else if (!d->selectPerfectIdMatch()) {
662GpgME::UserID Kleo::UserIDSelectionCombo::currentUserID()
const
664 return currentData(KeyList::UserIDRole).value<GpgME::UserID>();
667void Kleo::UserIDSelectionCombo::setCurrentUserID(
const GpgME::UserID &userID)
669 for (
auto i = 0; i < count(); i++) {
670 const auto &other = itemData(i, KeyList::UserIDRole).value<GpgME::UserID>();
671 if (!qstrcmp(userID.id(), other.id()) && !qstrcmp(userID.parent().primaryFingerprint(), other.parent().primaryFingerprint())) {
677 if (!d->selectPerfectIdMatch()) {
678 d->updateWithDefaultKey();
683void UserIDSelectionCombo::refreshKeys()
686 d->useWasEnabled =
true;
689 prependCustomItem(
QIcon(),
i18n(
"Loading keys ..."), QStringLiteral(
"-libkleo-loading-keys"));
692 d->cache->startKeyListing();
697 d->proxyModel->appendItem(icon, text, data,
toolTip);
700void UserIDSelectionCombo::appendCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
702 appendCustomItem(icon, text, data,
QString());
707 d->proxyModel->prependItem(icon, text, data,
toolTip);
710void UserIDSelectionCombo::prependCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
712 prependCustomItem(icon, text, data,
QString());
715void UserIDSelectionCombo::removeCustomItem(
const QVariant &data)
717 d->proxyModel->removeCustomItem(data);
720void Kleo::UserIDSelectionCombo::setDefaultKey(
const QString &fingerprint, GpgME::Protocol proto)
722 d->defaultKeys.insert(proto, fingerprint);
723 d->updateWithDefaultKey();
726void Kleo::UserIDSelectionCombo::setDefaultKey(
const QString &fingerprint)
728 setDefaultKey(fingerprint, GpgME::UnknownProtocol);
731QString Kleo::UserIDSelectionCombo::defaultKey(GpgME::Protocol proto)
const
733 return d->defaultKeys.value(proto);
736QString Kleo::UserIDSelectionCombo::defaultKey()
const
738 return defaultKey(GpgME::UnknownProtocol);
740#include "useridselectioncombo.moc"
742#include "moc_useridselectioncombo.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