7#include <config-libkleo.h>
9#include "useridselectioncombo.h"
11#include "progressbar.h"
13#include <libkleo/defaultkeyfilter.h>
14#include <libkleo/formatting.h>
15#include <libkleo/keycache.h>
16#include <libkleo/keyfiltermanager.h>
17#include <libkleo/keyhelpers.h>
18#include <libkleo/keylist.h>
19#include <libkleo/keylistmodel.h>
20#include <libkleo/keylistsortfilterproxymodel.h>
21#include <libkleo/useridproxymodel.h>
23#include <kleo_ui_debug.h>
25#include <KLocalizedString>
31#include <QSortFilterProxyModel>
35#include <gpgme++/key.h>
40Q_DECLARE_METATYPE(GpgME::Key)
44class SortFilterProxyModel :
public KeyListSortFilterProxyModel
49 using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
51 void setAlwaysAcceptedKey(
const QString &fingerprint)
53 if (fingerprint == mFingerprint) {
56 mFingerprint = fingerprint;
61 bool filterAcceptsRow(
int source_row,
const QModelIndex &source_parent)
const override
63 if (!mFingerprint.isEmpty()) {
64 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
65 const auto fingerprint = sourceModel()->
data(index, KeyList::FingerprintRole).toString();
66 if (fingerprint == mFingerprint) {
71 return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
78static QString formatUserID(
const GpgME::UserID &userID)
83 if (userID.parent().protocol() == GpgME::OpenPGP) {
87 const QGpgME::DN dn(userID.id());
88 name = dn[QStringLiteral(
"CN")];
89 email = dn[QStringLiteral(
"EMAIL")];
91 name = QGpgME::DN(userID.parent().userID(0).id())[QStringLiteral(
"CN")];
94 return email.
isEmpty() ? name : name.isEmpty() ? email :
i18nc(
"Name <email>",
"%1 <%2>", name, email);
102 SortAndFormatCertificatesProxyModel(KeyUsage::Flags usageFlags, QObject *parent =
nullptr)
103 : QSortFilterProxyModel{parent}
104 , mIconProvider{usageFlags}
109 bool lessThan(
const QModelIndex &left,
const QModelIndex &right)
const override
111 const auto leftUserId = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
112 const auto rightUserId = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
113 if (leftUserId.isNull()) {
116 if (rightUserId.isNull()) {
119 const auto leftNameAndEmail = formatUserID(leftUserId);
120 const auto rightNameAndEmail = formatUserID(rightUserId);
126 if (leftUserId.validity() != rightUserId.validity()) {
127 return leftUserId.validity() > rightUserId.validity();
132 for (
const GpgME::Subkey &s : leftUserId.parent().subkeys()) {
136 if (s.creationTime() > leftTime) {
137 leftTime = s.creationTime();
140 time_t rightTime = 0;
141 for (
const GpgME::Subkey &s : rightUserId.parent().subkeys()) {
145 if (s.creationTime() > rightTime) {
146 rightTime = s.creationTime();
149 if (rightTime != leftTime) {
150 return leftTime > rightTime;
154 return strcmp(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
158 QVariant data(
const QModelIndex &index,
int role)
const override
165 Q_ASSERT(!userId.isNull());
166 if (userId.isNull()) {
173 return Formatting::summaryLine(userId);
176 using namespace Kleo::Formatting;
177 return Kleo::Formatting::toolTip(userId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
180 return mIconProvider.icon(userId.parent());
183 return KeyFilterManager::instance()->font(userId.parent(), QFont());
191 Formatting::IconProvider mIconProvider;
207 CustomItemsProxyModel(QObject *parent =
nullptr)
208 : QSortFilterProxyModel(parent)
212 ~CustomItemsProxyModel()
override
214 qDeleteAll(mFrontItems);
215 qDeleteAll(mBackItems);
218 bool isCustomItem(
const int row)
const
223 void prependItem(
const QIcon &icon,
const QString &text,
const QVariant &data,
const QString &toolTip)
225 beginInsertRows(QModelIndex(), 0, 0);
226 mFrontItems.push_front(
new CustomItem{icon, text, data, toolTip});
230 void appendItem(
const QIcon &icon,
const QString &text,
const QVariant &data,
const QString &toolTip)
232 beginInsertRows(QModelIndex(), rowCount(), rowCount());
233 mBackItems.push_back(
new CustomItem{icon, text, data, toolTip});
237 void removeCustomItem(
const QVariant &data)
239 for (
int i = 0; i < mFrontItems.count(); ++i) {
240 if (mFrontItems[i]->data == data) {
241 beginRemoveRows(QModelIndex(), i, i);
242 delete mFrontItems.takeAt(i);
247 for (
int i = 0; i < mBackItems.count(); ++i) {
248 if (mBackItems[i]->data == data) {
250 beginRemoveRows(QModelIndex(), index, index);
251 delete mBackItems.takeAt(i);
258 int rowCount(
const QModelIndex &parent = QModelIndex())
const override
263 int columnCount(
const QModelIndex &parent = QModelIndex())
const override
272 QModelIndex mapToSource(
const QModelIndex &index)
const override
277 if (!isCustomItem(index.
row())) {
278 const int sourceRow = index.
row() - mFrontItems.count();
284 QModelIndex mapFromSource(
const QModelIndex &source_index)
const override
290 QModelIndex index(
int row,
int column,
const QModelIndex &parent = QModelIndex())
const override
292 if (row < 0 || row >= rowCount()) {
295 if (row < mFrontItems.count()) {
296 return createIndex(row, column, mFrontItems[row]);
311 QModelIndex parent(
const QModelIndex &)
const override
317 QVariant data(
const QModelIndex &index,
int role)
const override
323 if (isCustomItem(index.
row())) {
324 Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
332 case KeyList::UserIDRole:
345 QList<CustomItem *> mFrontItems;
346 QList<CustomItem *> mBackItems;
353class UserIDSelectionComboPrivate
356 UserIDSelectionComboPrivate(UserIDSelectionCombo *parent,
bool secretOnly_, KeyUsage::Flags usage)
358 , secretOnly{secretOnly_}
379 bool selectPerfectIdMatch()
const
381 if (mPerfectMatchMbox.isEmpty()) {
385 for (
int i = 0; i < proxyModel->rowCount(); ++i) {
386 const auto idx = proxyModel->index(i, 0, QModelIndex());
387 const auto userID = idx.data(KeyList::UserIDRole).value<GpgME::UserID>();
388 if (userID.isNull()) {
393 combo->setCurrentIndex(i);
402 void updateWithDefaultKey()
404 GpgME::Protocol filterProto = GpgME::UnknownProtocol;
406 const auto filter =
dynamic_cast<const DefaultKeyFilter *
>(sortFilterProxy->keyFilter().
get());
407 if (filter &&
filter->isOpenPGP() == DefaultKeyFilter::Set) {
408 filterProto = GpgME::OpenPGP;
409 }
else if (filter &&
filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
410 filterProto = GpgME::CMS;
413 QString defaultKey = defaultKeys.value(filterProto);
416 defaultKey = defaultKeys.value(GpgME::UnknownProtocol);
419 if (filterProto == GpgME::UnknownProtocol) {
420 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
422 const auto key = KeyCache::instance()->findByFingerprint(defaultKey.
toLatin1().
constData());
423 if (!key.isNull() && key.protocol() == filterProto) {
424 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
426 sortFilterProxy->setAlwaysAcceptedKey({});
429 q->setCurrentKey(defaultKey);
432 void storeCurrentSelectionBeforeModelChange()
434 userIDBeforeModelChange = q->currentUserID();
435 customItemBeforeModelChange = combo->currentData();
438 void restoreCurrentSelectionAfterModelChange()
440 if (!userIDBeforeModelChange.isNull()) {
441 q->setCurrentUserID(userIDBeforeModelChange);
442 }
else if (customItemBeforeModelChange.isValid()) {
443 const auto index = combo->findData(customItemBeforeModelChange);
445 combo->setCurrentIndex(index);
447 updateWithDefaultKey();
452 Kleo::AbstractKeyListModel *model =
nullptr;
453 UserIDProxyModel *userIdProxy =
nullptr;
454 SortFilterProxyModel *sortFilterProxy =
nullptr;
455 SortAndFormatCertificatesProxyModel *sortAndFormatProxy =
nullptr;
456 CustomItemsProxyModel *proxyModel =
nullptr;
457 QComboBox *combo =
nullptr;
458 QToolButton *button =
nullptr;
459 std::shared_ptr<Kleo::KeyCache> cache;
460 QMap<GpgME::Protocol, QString> defaultKeys;
461 bool wasEnabled =
false;
462 bool useWasEnabled =
false;
463 bool secretOnly =
false;
464 bool initialKeyListingDone =
false;
465 QString mPerfectMatchMbox;
466 GpgME::UserID userIDBeforeModelChange;
467 QVariant customItemBeforeModelChange;
468 KeyUsage::Flags usageFlags;
471 UserIDSelectionCombo *
const q;
478UserIDSelectionCombo::UserIDSelectionCombo(
QWidget *parent)
479 : UserIDSelectionCombo(true, KeyUsage::
None, parent)
483UserIDSelectionCombo::UserIDSelectionCombo(
bool secretOnly,
QWidget *parent)
484 : UserIDSelectionCombo(secretOnly, KeyUsage::
None, parent)
488UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flags usage,
QWidget *parent)
489 : UserIDSelectionCombo{false, usage, parent}
493UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flag usage,
QWidget *parent)
494 : UserIDSelectionCombo{false, usage, parent}
498UserIDSelectionCombo::UserIDSelectionCombo(
bool secretOnly, KeyUsage::Flags usage,
QWidget *parent)
500 , d(new UserIDSelectionComboPrivate(this, secretOnly, usage))
504 setAccessibleDescription(QStringLiteral(
" "));
505 d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(
this);
507 d->userIdProxy =
new UserIDProxyModel(
this);
508 d->userIdProxy->setSourceModel(d->model);
510 d->sortFilterProxy =
new SortFilterProxyModel(
this);
511 d->sortFilterProxy->setSourceModel(d->userIdProxy);
513 d->sortAndFormatProxy =
new SortAndFormatCertificatesProxyModel{usage,
this};
514 d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
516 d->sortAndFormatProxy->sort(0);
518 d->proxyModel =
new CustomItemsProxyModel{
this};
519 d->proxyModel->setSourceModel(d->sortAndFormatProxy);
522 layout->setContentsMargins({});
525 layout->addWidget(d->combo);
529 d->button->setToolTip(
i18nc(
"@info:tooltip",
"Show certificate list"));
530 d->button->setAccessibleName(
i18n(
"Show certificate list"));
531 layout->addWidget(d->button);
535 d->combo->setModel(d->proxyModel);
537 if (row >= 0 && row < d->proxyModel->rowCount()) {
538 if (d->proxyModel->isCustomItem(row)) {
539 Q_EMIT customItemSelected(d->combo->currentData(Qt::UserRole));
541 Q_EMIT currentKeyChanged(currentKey());
546 d->cache = Kleo::KeyCache::mutableInstance();
549 d->storeCurrentSelectionBeforeModelChange();
552 d->restoreCurrentSelectionAfterModelChange();
555 d->storeCurrentSelectionBeforeModelChange();
558 d->restoreCurrentSelectionAfterModelChange();
561 d->storeCurrentSelectionBeforeModelChange();
564 d->restoreCurrentSelectionAfterModelChange();
570UserIDSelectionCombo::~UserIDSelectionCombo() =
default;
572void UserIDSelectionCombo::init()
574 connect(d->cache.get(), &Kleo::KeyCache::keyListingDone,
this, [
this]() {
577 if (!d->initialKeyListingDone) {
578 d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
580 d->proxyModel->removeCustomItem(QStringLiteral(
"-libkleo-loading-keys"));
590 if (d->useWasEnabled) {
591 setEnabled(d->wasEnabled);
592 d->useWasEnabled = false;
594 Q_EMIT keyListingFinished();
597 connect(
this, &UserIDSelectionCombo::keyListingFinished,
this, [
this]() {
598 if (!d->initialKeyListingDone) {
599 d->updateWithDefaultKey();
600 d->initialKeyListingDone =
true;
604 if (!d->cache->initialized()) {
607 d->model->useKeyCache(
true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
608 Q_EMIT keyListingFinished();
612 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
616void UserIDSelectionCombo::setKeyFilter(
const std::shared_ptr<const KeyFilter> &kf)
618 d->sortFilterProxy->setKeyFilter(kf);
619 d->updateWithDefaultKey();
622std::shared_ptr<const KeyFilter> UserIDSelectionCombo::keyFilter()
const
624 return d->sortFilterProxy->keyFilter();
627void UserIDSelectionCombo::setIdFilter(
const QString &
id)
629 d->sortFilterProxy->setFilterRegularExpression(
id);
630 d->mPerfectMatchMbox = id;
631 d->updateWithDefaultKey();
634QString UserIDSelectionCombo::idFilter()
const
636 return d->sortFilterProxy->filterRegularExpression().pattern();
639GpgME::Key Kleo::UserIDSelectionCombo::currentKey()
const
641 return d->combo->currentData(KeyList::KeyRole).value<GpgME::Key>();
644void Kleo::UserIDSelectionCombo::setCurrentKey(
const GpgME::Key &key)
648 d->combo->setCurrentIndex(idx);
649 }
else if (!d->selectPerfectIdMatch()) {
650 d->updateWithDefaultKey();
655void Kleo::UserIDSelectionCombo::setCurrentKey(
const QString &fingerprint)
657 const auto cur = currentKey();
658 if (!cur.isNull() && !fingerprint.
isEmpty() && fingerprint == QLatin1StringView(cur.primaryFingerprint())) {
661 Q_EMIT currentKeyChanged(cur);
664 const int idx = d->combo->findData(fingerprint, KeyList::FingerprintRole,
Qt::MatchExactly);
666 d->combo->setCurrentIndex(idx);
667 }
else if (!d->selectPerfectIdMatch()) {
668 d->combo->setCurrentIndex(0);
673GpgME::UserID Kleo::UserIDSelectionCombo::currentUserID()
const
675 return d->combo->currentData(KeyList::UserIDRole).value<GpgME::UserID>();
678void Kleo::UserIDSelectionCombo::setCurrentUserID(
const GpgME::UserID &userID)
680 for (
auto i = 0; i < d->combo->count(); i++) {
681 const auto &other = d->combo->itemData(i, KeyList::UserIDRole).value<GpgME::UserID>();
682 if (!qstrcmp(userID.id(), other.id()) && !qstrcmp(userID.parent().primaryFingerprint(), other.parent().primaryFingerprint())) {
683 d->combo->setCurrentIndex(i);
688 if (!d->selectPerfectIdMatch()) {
689 d->updateWithDefaultKey();
694void UserIDSelectionCombo::refreshKeys()
697 d->useWasEnabled =
true;
700 prependCustomItem(QIcon(),
i18n(
"Loading keys ..."), QStringLiteral(
"-libkleo-loading-keys"));
701 d->combo->setCurrentIndex(0);
703 d->cache->startKeyListing();
706void UserIDSelectionCombo::appendCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data,
const QString &toolTip)
708 d->proxyModel->appendItem(icon, text, data,
toolTip);
711void UserIDSelectionCombo::appendCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
713 appendCustomItem(icon, text, data, QString());
716void UserIDSelectionCombo::prependCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data,
const QString &toolTip)
718 d->proxyModel->prependItem(icon, text, data,
toolTip);
721void UserIDSelectionCombo::prependCustomItem(
const QIcon &icon,
const QString &text,
const QVariant &data)
723 prependCustomItem(icon, text, data, QString());
726void UserIDSelectionCombo::removeCustomItem(
const QVariant &data)
728 d->proxyModel->removeCustomItem(data);
731void Kleo::UserIDSelectionCombo::setDefaultKey(
const QString &fingerprint, GpgME::Protocol proto)
733 d->defaultKeys.insert(proto, fingerprint);
734 d->updateWithDefaultKey();
737void Kleo::UserIDSelectionCombo::setDefaultKey(
const QString &fingerprint)
739 setDefaultKey(fingerprint, GpgME::UnknownProtocol);
742QString Kleo::UserIDSelectionCombo::defaultKey(GpgME::Protocol proto)
const
744 return d->defaultKeys.value(proto);
747QString Kleo::UserIDSelectionCombo::defaultKey()
const
749 return defaultKey(GpgME::UnknownProtocol);
752QComboBox *Kleo::UserIDSelectionCombo::combo()
const
757int Kleo::UserIDSelectionCombo::findUserId(
const GpgME::UserID &userId)
const
759 for (
int i = 0; i < combo()->model()->rowCount(); i++) {
760 if (Kleo::userIDsAreEqual(userId, combo()->model()->index(i, 0).data(KeyList::UserIDRole).value<GpgME::UserID>())) {
767#include "useridselectioncombo.moc"
769#include "moc_useridselectioncombo.cpp"
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 currentIndexChanged(int index)
QIcon fromTheme(const QString &name)
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)