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"