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/keyfiltermanager.h>
18#include <libkleo/keyhelpers.h>
19#include <libkleo/keylist.h>
20#include <libkleo/keylistmodel.h>
21#include <libkleo/keylistsortfilterproxymodel.h>
22#include <libkleo/useridproxymodel.h>
24#include <kleo_ui_debug.h>
26#include <KLocalizedString>
30#include <QSortFilterProxyModel>
34#include <gpgme++/key.h>
39Q_DECLARE_METATYPE(GpgME::Key)
43class SortFilterProxyModel :
public KeyListSortFilterProxyModel
48 using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
50 void setAlwaysAcceptedKey(
const QString &fingerprint)
52 if (fingerprint == mFingerprint) {
55 mFingerprint = fingerprint;
60 bool filterAcceptsRow(
int source_row,
const QModelIndex &source_parent)
const override
62 if (!mFingerprint.isEmpty()) {
63 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
64 const auto fingerprint = sourceModel()->
data(index, KeyList::FingerprintRole).toString();
65 if (fingerprint == mFingerprint) {
70 return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
77static QString formatUserID(
const GpgME::UserID &userID)
82 if (userID.parent().protocol() == GpgME::OpenPGP) {
87 name = dn[QStringLiteral(
"CN")];
88 email = dn[QStringLiteral(
"EMAIL")];
90 name =
Kleo::DN(userID.parent().userID(0).id())[QStringLiteral(
"CN")];
93 return email.
isEmpty() ? name : name.isEmpty() ? email :
i18nc(
"Name <email>",
"%1 <%2>", name, email);
103 , mIconProvider{usageFlags}
110 const auto leftUserId = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
111 const auto rightUserId = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
112 if (leftUserId.isNull()) {
115 if (rightUserId.isNull()) {
118 const auto leftNameAndEmail = formatUserID(leftUserId);
119 const auto rightNameAndEmail = formatUserID(rightUserId);
125 if (leftUserId.validity() != rightUserId.validity()) {
126 return leftUserId.validity() > rightUserId.validity();
131 for (
const GpgME::Subkey &s : leftUserId.parent().subkeys()) {
135 if (s.creationTime() > leftTime) {
136 leftTime = s.creationTime();
139 time_t rightTime = 0;
140 for (
const GpgME::Subkey &s : rightUserId.parent().subkeys()) {
144 if (s.creationTime() > rightTime) {
145 rightTime = s.creationTime();
148 if (rightTime != leftTime) {
149 return leftTime > rightTime;
153 return strcmp(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
164 Q_ASSERT(!userId.isNull());
165 if (userId.isNull()) {
172 const auto nameAndEmail = formatUserID(userId);
173 if (Kleo::KeyCache::instance()->pgpOnly()) {
174 return i18nc(
"Name <email> (validity, created: date)",
175 "%1 (%2, created: %3)",
177 Kleo::Formatting::complianceStringShort(userId),
178 Kleo::Formatting::creationDateString(userId.parent()));
180 return i18nc(
"Name <email> (validity, type, created: date)",
181 "%1 (%2, %3, created: %4)",
183 Kleo::Formatting::complianceStringShort(userId),
184 Formatting::displayName(userId.parent().protocol()),
185 Kleo::Formatting::creationDateString(userId.parent()));
189 using namespace Kleo::Formatting;
190 return Kleo::Formatting::toolTip(userId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
193 return mIconProvider.icon(userId.parent());
196 return KeyFilterManager::instance()->font(userId.parent(),
QFont());
204 Formatting::IconProvider mIconProvider;
220 CustomItemsProxyModel(
QObject *parent =
nullptr)
225 ~CustomItemsProxyModel()
override
227 qDeleteAll(mFrontItems);
228 qDeleteAll(mBackItems);
231 bool isCustomItem(
const int row)
const
239 mFrontItems.push_front(
new CustomItem{icon, text, data, toolTip});
245 beginInsertRows(
QModelIndex(), rowCount(), rowCount());
246 mBackItems.push_back(
new CustomItem{icon, text, data, toolTip});
250 void removeCustomItem(
const QVariant &data)
252 for (
int i = 0; i < mFrontItems.count(); ++i) {
253 if (mFrontItems[i]->data == data) {
255 delete mFrontItems.takeAt(i);
260 for (
int i = 0; i < mBackItems.count(); ++i) {
261 if (mBackItems[i]->data == data) {
264 delete mBackItems.takeAt(i);
290 if (!isCustomItem(index.
row())) {
291 const int sourceRow = index.
row() - mFrontItems.count();
305 if (row < 0 || row >= rowCount()) {
308 if (row < mFrontItems.count()) {
309 return createIndex(row, column, mFrontItems[row]);
336 if (isCustomItem(index.
row())) {
337 Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
345 case KeyList::UserIDRole:
366class UserIDSelectionComboPrivate
369 UserIDSelectionComboPrivate(UserIDSelectionCombo *parent,
bool secretOnly_,
KeyUsage::Flags usage)
371 , secretOnly{secretOnly_}
392 bool selectPerfectIdMatch()
const
394 if (mPerfectMatchMbox.
isEmpty()) {
398 for (
int i = 0; i < proxyModel->rowCount(); ++i) {
399 const auto idx = proxyModel->index(i, 0,
QModelIndex());
400 const auto userID = idx.data(KeyList::UserIDRole).value<GpgME::UserID>();
401 if (userID.isNull()) {
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 userIDBeforeModelChange = q->currentUserID();
448 customItemBeforeModelChange = combo->
currentData();
451 void restoreCurrentSelectionAfterModelChange()
453 if (!userIDBeforeModelChange.isNull()) {
454 q->setCurrentUserID(userIDBeforeModelChange);
455 }
else if (customItemBeforeModelChange.
isValid()) {
456 const auto index = combo->
findData(customItemBeforeModelChange);
460 updateWithDefaultKey();
465 Kleo::AbstractKeyListModel *model =
nullptr;
466 UserIDProxyModel *userIdProxy =
nullptr;
467 SortFilterProxyModel *sortFilterProxy =
nullptr;
468 SortAndFormatCertificatesProxyModel *sortAndFormatProxy =
nullptr;
469 CustomItemsProxyModel *proxyModel =
nullptr;
472 std::shared_ptr<Kleo::KeyCache> cache;
474 bool wasEnabled =
false;
475 bool useWasEnabled =
false;
476 bool secretOnly =
false;
477 bool initialKeyListingDone =
false;
479 GpgME::UserID userIDBeforeModelChange;
480 QVariant customItemBeforeModelChange;
484 UserIDSelectionCombo *
const q;
491UserIDSelectionCombo::UserIDSelectionCombo(
QWidget *parent)
492 : UserIDSelectionCombo(true, KeyUsage::
None, parent)
496UserIDSelectionCombo::UserIDSelectionCombo(
bool secretOnly,
QWidget *parent)
497 : UserIDSelectionCombo(secretOnly, KeyUsage::
None, parent)
502 : UserIDSelectionCombo{false, usage, parent}
506UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flag usage,
QWidget *parent)
507 : UserIDSelectionCombo{false, usage, parent}
513 , d(new UserIDSelectionComboPrivate(this, secretOnly, usage))
517 setAccessibleDescription(QStringLiteral(
" "));
518 d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(
this);
520 d->userIdProxy =
new UserIDProxyModel(
this);
521 d->userIdProxy->setSourceModel(d->model);
523 d->sortFilterProxy =
new SortFilterProxyModel(
this);
524 d->sortFilterProxy->setSourceModel(d->userIdProxy);
526 d->sortAndFormatProxy =
new SortAndFormatCertificatesProxyModel{usage,
this};
527 d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
529 d->sortAndFormatProxy->sort(0);
531 d->proxyModel =
new CustomItemsProxyModel{
this};
532 d->proxyModel->setSourceModel(d->sortAndFormatProxy);
535 layout->setContentsMargins({});
538 layout->addWidget(d->combo);
542 d->button->setToolTip(
i18nc(
"@info:tooltip",
"Show certificate list"));
543 d->button->setAccessibleName(
i18n(
"Show certificate list"));
544 layout->addWidget(d->button);
548 d->combo->setModel(d->proxyModel);
550 if (row >= 0 && row < d->proxyModel->rowCount()) {
551 if (d->proxyModel->isCustomItem(row)) {
552 Q_EMIT customItemSelected(d->combo->currentData(Qt::UserRole));
554 Q_EMIT currentKeyChanged(currentKey());
559 d->cache = Kleo::KeyCache::mutableInstance();
562 d->storeCurrentSelectionBeforeModelChange();
565 d->restoreCurrentSelectionAfterModelChange();
568 d->storeCurrentSelectionBeforeModelChange();
571 d->restoreCurrentSelectionAfterModelChange();
574 d->storeCurrentSelectionBeforeModelChange();
577 d->restoreCurrentSelectionAfterModelChange();
583UserIDSelectionCombo::~UserIDSelectionCombo() =
default;
585void UserIDSelectionCombo::init()
587 connect(d->cache.get(), &Kleo::KeyCache::keyListingDone,
this, [
this]() {
590 if (!d->initialKeyListingDone) {
591 d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
593 d->proxyModel->removeCustomItem(QStringLiteral(
"-libkleo-loading-keys"));
603 if (d->useWasEnabled) {
604 setEnabled(d->wasEnabled);
605 d->useWasEnabled = false;
607 Q_EMIT keyListingFinished();
610 connect(
this, &UserIDSelectionCombo::keyListingFinished,
this, [
this]() {
611 if (!d->initialKeyListingDone) {
612 d->updateWithDefaultKey();
613 d->initialKeyListingDone =
true;
617 if (!d->cache->initialized()) {
620 d->model->useKeyCache(
true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
621 Q_EMIT keyListingFinished();
625 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
629void UserIDSelectionCombo::setKeyFilter(
const std::shared_ptr<const KeyFilter> &kf)
631 d->sortFilterProxy->setKeyFilter(kf);
632 d->updateWithDefaultKey();
635std::shared_ptr<const KeyFilter> UserIDSelectionCombo::keyFilter()
const
637 return d->sortFilterProxy->keyFilter();
640void UserIDSelectionCombo::setIdFilter(
const QString &
id)
642 d->sortFilterProxy->setFilterRegularExpression(
id);
643 d->mPerfectMatchMbox = id;
644 d->updateWithDefaultKey();
647QString UserIDSelectionCombo::idFilter()
const
649 return d->sortFilterProxy->filterRegularExpression().pattern();
652GpgME::Key Kleo::UserIDSelectionCombo::currentKey()
const
654 return d->combo->currentData(KeyList::KeyRole).value<GpgME::Key>();
657void Kleo::UserIDSelectionCombo::setCurrentKey(
const GpgME::Key &key)
661 d->combo->setCurrentIndex(idx);
662 }
else if (!d->selectPerfectIdMatch()) {
663 d->updateWithDefaultKey();
668void Kleo::UserIDSelectionCombo::setCurrentKey(
const QString &fingerprint)
670 const auto cur = currentKey();
674 Q_EMIT currentKeyChanged(cur);
677 const int idx = d->combo->findData(fingerprint, KeyList::FingerprintRole,
Qt::MatchExactly);
679 d->combo->setCurrentIndex(idx);
680 }
else if (!d->selectPerfectIdMatch()) {
681 d->combo->setCurrentIndex(0);
686GpgME::UserID Kleo::UserIDSelectionCombo::currentUserID()
const
688 return d->combo->currentData(KeyList::UserIDRole).value<GpgME::UserID>();
691void Kleo::UserIDSelectionCombo::setCurrentUserID(
const GpgME::UserID &userID)
693 for (
auto i = 0; i < d->combo->count(); i++) {
694 const auto &other = d->combo->itemData(i, KeyList::UserIDRole).value<GpgME::UserID>();
695 if (!qstrcmp(userID.id(), other.id()) && !qstrcmp(userID.parent().primaryFingerprint(), other.parent().primaryFingerprint())) {
696 d->combo->setCurrentIndex(i);
701 if (!d->selectPerfectIdMatch()) {
702 d->updateWithDefaultKey();
707void UserIDSelectionCombo::refreshKeys()
710 d->useWasEnabled =
true;
713 prependCustomItem(
QIcon(),
i18n(
"Loading keys ..."), QStringLiteral(
"-libkleo-loading-keys"));
714 d->combo->setCurrentIndex(0);
716 d->cache->startKeyListing();
721 d->proxyModel->appendItem(icon, text, data,
toolTip);
724void UserIDSelectionCombo::appendCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
726 appendCustomItem(icon, text, data,
QString());
731 d->proxyModel->prependItem(icon, text, data,
toolTip);
734void UserIDSelectionCombo::prependCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
736 prependCustomItem(icon, text, data,
QString());
739void UserIDSelectionCombo::removeCustomItem(
const QVariant &data)
741 d->proxyModel->removeCustomItem(data);
744void Kleo::UserIDSelectionCombo::setDefaultKey(
const QString &fingerprint, GpgME::Protocol proto)
746 d->defaultKeys.insert(proto, fingerprint);
747 d->updateWithDefaultKey();
750void Kleo::UserIDSelectionCombo::setDefaultKey(
const QString &fingerprint)
752 setDefaultKey(fingerprint, GpgME::UnknownProtocol);
755QString Kleo::UserIDSelectionCombo::defaultKey(GpgME::Protocol proto)
const
757 return d->defaultKeys.value(proto);
760QString Kleo::UserIDSelectionCombo::defaultKey()
const
762 return defaultKey(GpgME::UnknownProtocol);
765QComboBox *Kleo::UserIDSelectionCombo::combo()
const
770int Kleo::UserIDSelectionCombo::findUserId(
const GpgME::UserID &userId)
const
772 for (
int i = 0; i < combo()->model()->rowCount(); i++) {
773 if (Kleo::userIDsAreEqual(userId, combo()->model()->index(i, 0).data(KeyList::UserIDRole).value<GpgME::UserID>())) {
780#include "useridselectioncombo.moc"
782#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...)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
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
QIcon fromTheme(const QString &name)
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