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"