File indexing completed on 2025-01-05 04:55:51

0001 /*  This file is part of Kleopatra, the KDE keymanager
0002     SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <config-libkleo.h>
0008 
0009 #include "useridselectioncombo.h"
0010 
0011 #include "progressbar.h"
0012 
0013 #include <libkleo/defaultkeyfilter.h>
0014 #include <libkleo/dn.h>
0015 #include <libkleo/formatting.h>
0016 #include <libkleo/keycache.h>
0017 #include <libkleo/keylist.h>
0018 #include <libkleo/keylistmodel.h>
0019 #include <libkleo/keylistsortfilterproxymodel.h>
0020 #include <libkleo/useridproxymodel.h>
0021 
0022 #include <kleo_ui_debug.h>
0023 
0024 #include <KLocalizedString>
0025 
0026 #include <QList>
0027 #include <QSortFilterProxyModel>
0028 #include <QTimer>
0029 
0030 #include <gpgme++/key.h>
0031 
0032 using namespace Kleo;
0033 
0034 #if !UNITY_BUILD
0035 Q_DECLARE_METATYPE(GpgME::Key)
0036 #endif
0037 namespace
0038 {
0039 class SortFilterProxyModel : public KeyListSortFilterProxyModel
0040 {
0041     Q_OBJECT
0042 
0043 public:
0044     using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
0045 
0046     void setAlwaysAcceptedKey(const QString &fingerprint)
0047     {
0048         if (fingerprint == mFingerprint) {
0049             return;
0050         }
0051         mFingerprint = fingerprint;
0052         invalidate();
0053     }
0054 
0055 protected:
0056     bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
0057     {
0058         if (!mFingerprint.isEmpty()) {
0059             const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
0060             const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString();
0061             if (fingerprint == mFingerprint) {
0062                 return true;
0063             }
0064         }
0065 
0066         return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
0067     }
0068 
0069 private:
0070     QString mFingerprint;
0071 };
0072 
0073 static QString formatUserID(const GpgME::UserID &userID)
0074 {
0075     QString name;
0076     QString email;
0077 
0078     if (userID.parent().protocol() == GpgME::OpenPGP) {
0079         name = QString::fromUtf8(userID.name());
0080         email = QString::fromUtf8(userID.email());
0081     } else {
0082         const Kleo::DN dn(userID.id());
0083         name = dn[QStringLiteral("CN")];
0084         email = dn[QStringLiteral("EMAIL")];
0085         if (name.isEmpty()) {
0086             name = Kleo::DN(userID.parent().userID(0).id())[QStringLiteral("CN")];
0087         }
0088     }
0089     return email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name <email>", "%1 <%2>", name, email);
0090 }
0091 
0092 class SortAndFormatCertificatesProxyModel : public QSortFilterProxyModel
0093 {
0094     Q_OBJECT
0095 
0096 public:
0097     SortAndFormatCertificatesProxyModel(KeyUsage::Flags usageFlags, QObject *parent = nullptr)
0098         : QSortFilterProxyModel{parent}
0099         , mIconProvider{usageFlags}
0100     {
0101     }
0102 
0103 private:
0104     bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
0105     {
0106         const auto leftUserId = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
0107         const auto rightUserId = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
0108         if (leftUserId.isNull()) {
0109             return false;
0110         }
0111         if (rightUserId.isNull()) {
0112             return true;
0113         }
0114         const auto leftNameAndEmail = formatUserID(leftUserId);
0115         const auto rightNameAndEmail = formatUserID(rightUserId);
0116         const int cmp = QString::localeAwareCompare(leftNameAndEmail, rightNameAndEmail);
0117         if (cmp) {
0118             return cmp < 0;
0119         }
0120 
0121         if (leftUserId.validity() != rightUserId.validity()) {
0122             return leftUserId.validity() > rightUserId.validity();
0123         }
0124 
0125         /* Both have the same validity, check which one is newer. */
0126         time_t leftTime = 0;
0127         for (const GpgME::Subkey &s : leftUserId.parent().subkeys()) {
0128             if (s.isBad()) {
0129                 continue;
0130             }
0131             if (s.creationTime() > leftTime) {
0132                 leftTime = s.creationTime();
0133             }
0134         }
0135         time_t rightTime = 0;
0136         for (const GpgME::Subkey &s : rightUserId.parent().subkeys()) {
0137             if (s.isBad()) {
0138                 continue;
0139             }
0140             if (s.creationTime() > rightTime) {
0141                 rightTime = s.creationTime();
0142             }
0143         }
0144         if (rightTime != leftTime) {
0145             return leftTime > rightTime;
0146         }
0147 
0148         // as final resort we compare the fingerprints
0149         return strcmp(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
0150     }
0151 
0152 protected:
0153     QVariant data(const QModelIndex &index, int role) const override
0154     {
0155         if (!index.isValid()) {
0156             return QVariant();
0157         }
0158 
0159         const auto userId = QSortFilterProxyModel::data(index, KeyList::UserIDRole).value<GpgME::UserID>();
0160         Q_ASSERT(!userId.isNull());
0161         if (userId.isNull()) {
0162             return QVariant();
0163         }
0164 
0165         switch (role) {
0166         case Qt::DisplayRole:
0167         case Qt::AccessibleTextRole: {
0168             const auto nameAndEmail = formatUserID(userId);
0169             if (Kleo::KeyCache::instance()->pgpOnly()) {
0170                 return i18nc("Name <email> (validity, created: date)",
0171                              "%1 (%2, created: %3)",
0172                              nameAndEmail,
0173                              Kleo::Formatting::complianceStringShort(userId),
0174                              Kleo::Formatting::creationDateString(userId.parent()));
0175             } else {
0176                 return i18nc("Name <email> (validity, type, created: date)",
0177                              "%1 (%2, %3, created: %4)",
0178                              nameAndEmail,
0179                              Kleo::Formatting::complianceStringShort(userId),
0180                              Formatting::displayName(userId.parent().protocol()),
0181                              Kleo::Formatting::creationDateString(userId.parent()));
0182             }
0183         }
0184         case Qt::ToolTipRole: {
0185             using namespace Kleo::Formatting;
0186             return Kleo::Formatting::toolTip(userId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
0187         }
0188         case Qt::DecorationRole: {
0189             return mIconProvider.icon(userId.parent());
0190         }
0191         default:
0192             return QSortFilterProxyModel::data(index, role);
0193         }
0194     }
0195 
0196 private:
0197     Formatting::IconProvider mIconProvider;
0198 };
0199 
0200 class CustomItemsProxyModel : public QSortFilterProxyModel
0201 {
0202     Q_OBJECT
0203 
0204 private:
0205     struct CustomItem {
0206         QIcon icon;
0207         QString text;
0208         QVariant data;
0209         QString toolTip;
0210     };
0211 
0212 public:
0213     CustomItemsProxyModel(QObject *parent = nullptr)
0214         : QSortFilterProxyModel(parent)
0215     {
0216     }
0217 
0218     ~CustomItemsProxyModel() override
0219     {
0220         qDeleteAll(mFrontItems);
0221         qDeleteAll(mBackItems);
0222     }
0223 
0224     bool isCustomItem(const int row) const
0225     {
0226         return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount();
0227     }
0228 
0229     void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
0230     {
0231         beginInsertRows(QModelIndex(), 0, 0);
0232         mFrontItems.push_front(new CustomItem{icon, text, data, toolTip});
0233         endInsertRows();
0234     }
0235 
0236     void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
0237     {
0238         beginInsertRows(QModelIndex(), rowCount(), rowCount());
0239         mBackItems.push_back(new CustomItem{icon, text, data, toolTip});
0240         endInsertRows();
0241     }
0242 
0243     void removeCustomItem(const QVariant &data)
0244     {
0245         for (int i = 0; i < mFrontItems.count(); ++i) {
0246             if (mFrontItems[i]->data == data) {
0247                 beginRemoveRows(QModelIndex(), i, i);
0248                 delete mFrontItems.takeAt(i);
0249                 endRemoveRows();
0250                 return;
0251             }
0252         }
0253         for (int i = 0; i < mBackItems.count(); ++i) {
0254             if (mBackItems[i]->data == data) {
0255                 const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i;
0256                 beginRemoveRows(QModelIndex(), index, index);
0257                 delete mBackItems.takeAt(i);
0258                 endRemoveRows();
0259                 return;
0260             }
0261         }
0262     }
0263 
0264     int rowCount(const QModelIndex &parent = QModelIndex()) const override
0265     {
0266         return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count();
0267     }
0268 
0269     int columnCount(const QModelIndex &parent = QModelIndex()) const override
0270     {
0271         Q_UNUSED(parent)
0272         // pretend that there is only one column to workaround a bug in
0273         // QAccessibleTable which provides the accessibility interface for the
0274         // pop-up of the combo box
0275         return 1;
0276     }
0277 
0278     QModelIndex mapToSource(const QModelIndex &index) const override
0279     {
0280         if (!index.isValid()) {
0281             return {};
0282         }
0283         if (!isCustomItem(index.row())) {
0284             const int sourceRow = index.row() - mFrontItems.count();
0285             return QSortFilterProxyModel::mapToSource(createIndex(sourceRow, index.column(), index.internalPointer()));
0286         }
0287         return {};
0288     }
0289 
0290     QModelIndex mapFromSource(const QModelIndex &source_index) const override
0291     {
0292         const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
0293         return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer());
0294     }
0295 
0296     QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
0297     {
0298         if (row < 0 || row >= rowCount()) {
0299             return {};
0300         }
0301         if (row < mFrontItems.count()) {
0302             return createIndex(row, column, mFrontItems[row]);
0303         } else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) {
0304             return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]);
0305         } else {
0306             const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent);
0307             return createIndex(row, column, mi.internalPointer());
0308         }
0309     }
0310 
0311     Qt::ItemFlags flags(const QModelIndex &index) const override
0312     {
0313         Q_UNUSED(index)
0314         return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
0315     }
0316 
0317     QModelIndex parent(const QModelIndex &) const override
0318     {
0319         // Flat list
0320         return {};
0321     }
0322 
0323     QVariant data(const QModelIndex &index, int role) const override
0324     {
0325         if (!index.isValid()) {
0326             return QVariant();
0327         }
0328 
0329         if (isCustomItem(index.row())) {
0330             Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
0331             auto ci = static_cast<CustomItem *>(index.internalPointer());
0332             switch (role) {
0333             case Qt::DisplayRole:
0334                 return ci->text;
0335             case Qt::DecorationRole:
0336                 return ci->icon;
0337             case Qt::UserRole:
0338                 return ci->data;
0339             case Qt::ToolTipRole:
0340                 return ci->toolTip;
0341             default:
0342                 return QVariant();
0343             }
0344         }
0345 
0346         return QSortFilterProxyModel::data(index, role);
0347     }
0348 
0349 private:
0350     QList<CustomItem *> mFrontItems;
0351     QList<CustomItem *> mBackItems;
0352 };
0353 
0354 } // anonymous namespace
0355 
0356 namespace Kleo
0357 {
0358 class UserIDSelectionComboPrivate
0359 {
0360 public:
0361     UserIDSelectionComboPrivate(UserIDSelectionCombo *parent, bool secretOnly_, KeyUsage::Flags usage)
0362         : wasEnabled(true)
0363         , secretOnly{secretOnly_}
0364         , usageFlags{usage}
0365         , q{parent}
0366     {
0367     }
0368 
0369     /* Selects the first key with a UID addrSpec that matches
0370      * the mPerfectMatchMbox variable.
0371      *
0372      * The idea here is that if there are keys like:
0373      *
0374      * tom-store@abc.com
0375      * susi-store@abc.com
0376      * store@abc.com
0377      *
0378      * And the user wants to send a mail to "store@abc.com"
0379      * the filter should still show tom and susi (because they
0380      * both are part of store) but the key for "store" should
0381      * be preselected.
0382      *
0383      * Returns true if one was selected. False otherwise. */
0384     bool selectPerfectIdMatch() const
0385     {
0386         if (mPerfectMatchMbox.isEmpty()) {
0387             return false;
0388         }
0389 
0390         for (int i = 0; i < proxyModel->rowCount(); ++i) {
0391             const auto idx = proxyModel->index(i, 0, QModelIndex());
0392             const auto userID = idx.data(KeyList::UserIDRole).value<GpgME::UserID>();
0393             if (userID.isNull()) {
0394                 // WTF?
0395                 continue;
0396             }
0397             if (QString::fromStdString(userID.addrSpec()) == mPerfectMatchMbox) {
0398                 q->setCurrentIndex(i);
0399                 return true;
0400             }
0401         }
0402         return false;
0403     }
0404 
0405     /* Updates the current key with the default key if the key matches
0406      * the current key filter. */
0407     void updateWithDefaultKey()
0408     {
0409         GpgME::Protocol filterProto = GpgME::UnknownProtocol;
0410 
0411         const auto filter = dynamic_cast<const DefaultKeyFilter *>(sortFilterProxy->keyFilter().get());
0412         if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) {
0413             filterProto = GpgME::OpenPGP;
0414         } else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
0415             filterProto = GpgME::CMS;
0416         }
0417 
0418         QString defaultKey = defaultKeys.value(filterProto);
0419         if (defaultKey.isEmpty()) {
0420             // Fallback to unknown protocol
0421             defaultKey = defaultKeys.value(GpgME::UnknownProtocol);
0422         }
0423         // make sure that the default key is not filtered out unless it has the wrong protocol
0424         if (filterProto == GpgME::UnknownProtocol) {
0425             sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
0426         } else {
0427             const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData());
0428             if (!key.isNull() && key.protocol() == filterProto) {
0429                 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
0430             } else {
0431                 sortFilterProxy->setAlwaysAcceptedKey({});
0432             }
0433         }
0434         q->setCurrentKey(defaultKey);
0435     }
0436 
0437     void storeCurrentSelectionBeforeModelChange()
0438     {
0439         userIDBeforeModelChange = q->currentUserID();
0440         customItemBeforeModelChange = q->currentData();
0441     }
0442 
0443     void restoreCurrentSelectionAfterModelChange()
0444     {
0445         if (!userIDBeforeModelChange.isNull()) {
0446             q->setCurrentUserID(userIDBeforeModelChange);
0447         } else if (customItemBeforeModelChange.isValid()) {
0448             const auto index = q->findData(customItemBeforeModelChange);
0449             if (index != -1) {
0450                 q->setCurrentIndex(index);
0451             } else {
0452                 updateWithDefaultKey();
0453             }
0454         }
0455     }
0456 
0457     Kleo::AbstractKeyListModel *model = nullptr;
0458     UserIDProxyModel *userIdProxy = nullptr;
0459     SortFilterProxyModel *sortFilterProxy = nullptr;
0460     SortAndFormatCertificatesProxyModel *sortAndFormatProxy = nullptr;
0461     CustomItemsProxyModel *proxyModel = nullptr;
0462     std::shared_ptr<Kleo::KeyCache> cache;
0463     QMap<GpgME::Protocol, QString> defaultKeys;
0464     bool wasEnabled = false;
0465     bool useWasEnabled = false;
0466     bool secretOnly = false;
0467     bool initialKeyListingDone = false;
0468     QString mPerfectMatchMbox;
0469     GpgME::UserID userIDBeforeModelChange;
0470     QVariant customItemBeforeModelChange;
0471     KeyUsage::Flags usageFlags;
0472 
0473 private:
0474     UserIDSelectionCombo *const q;
0475 };
0476 
0477 }
0478 
0479 using namespace Kleo;
0480 
0481 UserIDSelectionCombo::UserIDSelectionCombo(QWidget *parent)
0482     : UserIDSelectionCombo(true, KeyUsage::None, parent)
0483 {
0484 }
0485 
0486 UserIDSelectionCombo::UserIDSelectionCombo(bool secretOnly, QWidget *parent)
0487     : UserIDSelectionCombo(secretOnly, KeyUsage::None, parent)
0488 {
0489 }
0490 
0491 UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flags usage, QWidget *parent)
0492     : UserIDSelectionCombo{false, usage, parent}
0493 {
0494 }
0495 
0496 UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flag usage, QWidget *parent)
0497     : UserIDSelectionCombo{false, usage, parent}
0498 {
0499 }
0500 
0501 UserIDSelectionCombo::UserIDSelectionCombo(bool secretOnly, KeyUsage::Flags usage, QWidget *parent)
0502     : QComboBox(parent)
0503     , d(new UserIDSelectionComboPrivate(this, secretOnly, usage))
0504 {
0505     // set a non-empty string as accessible description to prevent screen readers
0506     // from reading the tool tip which isn't meant for screen readers
0507     setAccessibleDescription(QStringLiteral(" "));
0508     d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this);
0509 
0510     d->userIdProxy = new UserIDProxyModel(this);
0511     d->userIdProxy->setSourceModel(d->model);
0512 
0513     d->sortFilterProxy = new SortFilterProxyModel(this);
0514     d->sortFilterProxy->setSourceModel(d->userIdProxy);
0515 
0516     d->sortAndFormatProxy = new SortAndFormatCertificatesProxyModel{usage, this};
0517     d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
0518     // initialize dynamic sorting
0519     d->sortAndFormatProxy->sort(0);
0520 
0521     d->proxyModel = new CustomItemsProxyModel{this};
0522     d->proxyModel->setSourceModel(d->sortAndFormatProxy);
0523 
0524     setModel(d->proxyModel);
0525     connect(this, &QComboBox::currentIndexChanged, this, [this](int row) {
0526         if (row >= 0 && row < d->proxyModel->rowCount()) {
0527             if (d->proxyModel->isCustomItem(row)) {
0528                 Q_EMIT customItemSelected(currentData(Qt::UserRole));
0529             } else {
0530                 Q_EMIT currentKeyChanged(currentKey());
0531             }
0532         }
0533     });
0534 
0535     d->cache = Kleo::KeyCache::mutableInstance();
0536 
0537     connect(model(), &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() {
0538         d->storeCurrentSelectionBeforeModelChange();
0539     });
0540     connect(model(), &QAbstractItemModel::rowsInserted, this, [this]() {
0541         d->restoreCurrentSelectionAfterModelChange();
0542     });
0543     connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this]() {
0544         d->storeCurrentSelectionBeforeModelChange();
0545     });
0546     connect(model(), &QAbstractItemModel::rowsRemoved, this, [this]() {
0547         d->restoreCurrentSelectionAfterModelChange();
0548     });
0549     connect(model(), &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
0550         d->storeCurrentSelectionBeforeModelChange();
0551     });
0552     connect(model(), &QAbstractItemModel::modelReset, this, [this]() {
0553         d->restoreCurrentSelectionAfterModelChange();
0554     });
0555 
0556     QTimer::singleShot(0, this, &UserIDSelectionCombo::init);
0557 }
0558 
0559 UserIDSelectionCombo::~UserIDSelectionCombo() = default;
0560 
0561 void UserIDSelectionCombo::init()
0562 {
0563     connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() {
0564         // Set useKeyCache ensures that the cache is populated
0565         // so this can be a blocking call if the cache is not initialized
0566         if (!d->initialKeyListingDone) {
0567             d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
0568         }
0569         d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys"));
0570 
0571         // We use the useWasEnabled state variable to decide if we should
0572         // change the enable / disable state based on the keylist done signal.
0573         // If we triggered the refresh useWasEnabled is true and we want to
0574         // enable / disable again after our refresh, as the refresh disabled it.
0575         //
0576         // But if a keyListingDone signal comes from just a generic refresh
0577         // triggered by someone else we don't want to change the enable / disable
0578         // state.
0579         if (d->useWasEnabled) {
0580             setEnabled(d->wasEnabled);
0581             d->useWasEnabled = false;
0582         }
0583         Q_EMIT keyListingFinished();
0584     });
0585 
0586     connect(this, &UserIDSelectionCombo::keyListingFinished, this, [this]() {
0587         if (!d->initialKeyListingDone) {
0588             d->updateWithDefaultKey();
0589             d->initialKeyListingDone = true;
0590         }
0591     });
0592 
0593     if (!d->cache->initialized()) {
0594         refreshKeys();
0595     } else {
0596         d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
0597         Q_EMIT keyListingFinished();
0598     }
0599 
0600     connect(this, &QComboBox::currentIndexChanged, this, [this]() {
0601         setToolTip(currentData(Qt::ToolTipRole).toString());
0602     });
0603 }
0604 
0605 void UserIDSelectionCombo::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
0606 {
0607     d->sortFilterProxy->setKeyFilter(kf);
0608     d->updateWithDefaultKey();
0609 }
0610 
0611 std::shared_ptr<const KeyFilter> UserIDSelectionCombo::keyFilter() const
0612 {
0613     return d->sortFilterProxy->keyFilter();
0614 }
0615 
0616 void UserIDSelectionCombo::setIdFilter(const QString &id)
0617 {
0618     d->sortFilterProxy->setFilterRegularExpression(id);
0619     d->mPerfectMatchMbox = id;
0620     d->updateWithDefaultKey();
0621 }
0622 
0623 QString UserIDSelectionCombo::idFilter() const
0624 {
0625     return d->sortFilterProxy->filterRegularExpression().pattern();
0626 }
0627 
0628 GpgME::Key Kleo::UserIDSelectionCombo::currentKey() const
0629 {
0630     return currentData(KeyList::KeyRole).value<GpgME::Key>();
0631 }
0632 
0633 void Kleo::UserIDSelectionCombo::setCurrentKey(const GpgME::Key &key)
0634 {
0635     const int idx = findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly);
0636     if (idx > -1) {
0637         setCurrentIndex(idx);
0638     } else if (!d->selectPerfectIdMatch()) {
0639         d->updateWithDefaultKey();
0640     }
0641     setToolTip(currentData(Qt::ToolTipRole).toString());
0642 }
0643 
0644 void Kleo::UserIDSelectionCombo::setCurrentKey(const QString &fingerprint)
0645 {
0646     const auto cur = currentKey();
0647     if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1StringView(cur.primaryFingerprint())) {
0648         // already set; still emit a changed signal because the current key may
0649         // have become the item at the current index by changes in the underlying model
0650         Q_EMIT currentKeyChanged(cur);
0651         return;
0652     }
0653     const int idx = findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly);
0654     if (idx > -1) {
0655         setCurrentIndex(idx);
0656     } else if (!d->selectPerfectIdMatch()) {
0657         setCurrentIndex(0);
0658     }
0659     setToolTip(currentData(Qt::ToolTipRole).toString());
0660 }
0661 
0662 GpgME::UserID Kleo::UserIDSelectionCombo::currentUserID() const
0663 {
0664     return currentData(KeyList::UserIDRole).value<GpgME::UserID>();
0665 }
0666 
0667 void Kleo::UserIDSelectionCombo::setCurrentUserID(const GpgME::UserID &userID)
0668 {
0669     for (auto i = 0; i < count(); i++) {
0670         const auto &other = itemData(i, KeyList::UserIDRole).value<GpgME::UserID>();
0671         if (!qstrcmp(userID.id(), other.id()) && !qstrcmp(userID.parent().primaryFingerprint(), other.parent().primaryFingerprint())) {
0672             setCurrentIndex(i);
0673             setToolTip(currentData(Qt::ToolTipRole).toString());
0674             return;
0675         }
0676     }
0677     if (!d->selectPerfectIdMatch()) {
0678         d->updateWithDefaultKey();
0679         setToolTip(currentData(Qt::ToolTipRole).toString());
0680     }
0681 }
0682 
0683 void UserIDSelectionCombo::refreshKeys()
0684 {
0685     d->wasEnabled = isEnabled();
0686     d->useWasEnabled = true;
0687     setEnabled(false);
0688     const bool wasBlocked = blockSignals(true);
0689     prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys"));
0690     setCurrentIndex(0);
0691     blockSignals(wasBlocked);
0692     d->cache->startKeyListing();
0693 }
0694 
0695 void UserIDSelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
0696 {
0697     d->proxyModel->appendItem(icon, text, data, toolTip);
0698 }
0699 
0700 void UserIDSelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
0701 {
0702     appendCustomItem(icon, text, data, QString());
0703 }
0704 
0705 void UserIDSelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
0706 {
0707     d->proxyModel->prependItem(icon, text, data, toolTip);
0708 }
0709 
0710 void UserIDSelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
0711 {
0712     prependCustomItem(icon, text, data, QString());
0713 }
0714 
0715 void UserIDSelectionCombo::removeCustomItem(const QVariant &data)
0716 {
0717     d->proxyModel->removeCustomItem(data);
0718 }
0719 
0720 void Kleo::UserIDSelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto)
0721 {
0722     d->defaultKeys.insert(proto, fingerprint);
0723     d->updateWithDefaultKey();
0724 }
0725 
0726 void Kleo::UserIDSelectionCombo::setDefaultKey(const QString &fingerprint)
0727 {
0728     setDefaultKey(fingerprint, GpgME::UnknownProtocol);
0729 }
0730 
0731 QString Kleo::UserIDSelectionCombo::defaultKey(GpgME::Protocol proto) const
0732 {
0733     return d->defaultKeys.value(proto);
0734 }
0735 
0736 QString Kleo::UserIDSelectionCombo::defaultKey() const
0737 {
0738     return defaultKey(GpgME::UnknownProtocol);
0739 }
0740 #include "useridselectioncombo.moc"
0741 
0742 #include "moc_useridselectioncombo.cpp"