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

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