Libkleo

useridselectioncombo.cpp
1/* This file is part of Kleopatra, the KDE keymanager
2 SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include <config-libkleo.h>
8
9#include "useridselectioncombo.h"
10
11#include "progressbar.h"
12
13#include <libkleo/defaultkeyfilter.h>
14#include <libkleo/dn.h>
15#include <libkleo/formatting.h>
16#include <libkleo/keycache.h>
17#include <libkleo/keyfiltermanager.h>
18#include <libkleo/keyhelpers.h>
19#include <libkleo/keylist.h>
20#include <libkleo/keylistmodel.h>
21#include <libkleo/keylistsortfilterproxymodel.h>
22#include <libkleo/useridproxymodel.h>
23
24#include <kleo_ui_debug.h>
25
26#include <KLocalizedString>
27
28#include <QHBoxLayout>
29#include <QList>
30#include <QSortFilterProxyModel>
31#include <QTimer>
32#include <QToolButton>
33
34#include <gpgme++/key.h>
35
36using namespace Kleo;
37
38#if !UNITY_BUILD
39Q_DECLARE_METATYPE(GpgME::Key)
40#endif
41namespace
42{
43class SortFilterProxyModel : public KeyListSortFilterProxyModel
44{
45 Q_OBJECT
46
47public:
48 using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
49
50 void setAlwaysAcceptedKey(const QString &fingerprint)
51 {
52 if (fingerprint == mFingerprint) {
53 return;
54 }
55 mFingerprint = fingerprint;
56 invalidate();
57 }
58
59protected:
60 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
61 {
62 if (!mFingerprint.isEmpty()) {
63 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
64 const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString();
65 if (fingerprint == mFingerprint) {
66 return true;
67 }
68 }
69
70 return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
71 }
72
73private:
74 QString mFingerprint;
75};
76
77static QString formatUserID(const GpgME::UserID &userID)
78{
79 QString name;
80 QString email;
81
82 if (userID.parent().protocol() == GpgME::OpenPGP) {
83 name = QString::fromUtf8(userID.name());
84 email = QString::fromUtf8(userID.email());
85 } else {
86 const Kleo::DN dn(userID.id());
87 name = dn[QStringLiteral("CN")];
88 email = dn[QStringLiteral("EMAIL")];
89 if (name.isEmpty()) {
90 name = Kleo::DN(userID.parent().userID(0).id())[QStringLiteral("CN")];
91 }
92 }
93 return email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name <email>", "%1 <%2>", name, email);
94}
95
96class SortAndFormatCertificatesProxyModel : public QSortFilterProxyModel
97{
98 Q_OBJECT
99
100public:
101 SortAndFormatCertificatesProxyModel(KeyUsage::Flags usageFlags, QObject *parent = nullptr)
102 : QSortFilterProxyModel{parent}
103 , mIconProvider{usageFlags}
104 {
105 }
106
107private:
108 bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
109 {
110 const auto leftUserId = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
111 const auto rightUserId = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
112 if (leftUserId.isNull()) {
113 return false;
114 }
115 if (rightUserId.isNull()) {
116 return true;
117 }
118 const auto leftNameAndEmail = formatUserID(leftUserId);
119 const auto rightNameAndEmail = formatUserID(rightUserId);
120 const int cmp = QString::localeAwareCompare(leftNameAndEmail, rightNameAndEmail);
121 if (cmp) {
122 return cmp < 0;
123 }
124
125 if (leftUserId.validity() != rightUserId.validity()) {
126 return leftUserId.validity() > rightUserId.validity();
127 }
128
129 /* Both have the same validity, check which one is newer. */
130 time_t leftTime = 0;
131 for (const GpgME::Subkey &s : leftUserId.parent().subkeys()) {
132 if (s.isBad()) {
133 continue;
134 }
135 if (s.creationTime() > leftTime) {
136 leftTime = s.creationTime();
137 }
138 }
139 time_t rightTime = 0;
140 for (const GpgME::Subkey &s : rightUserId.parent().subkeys()) {
141 if (s.isBad()) {
142 continue;
143 }
144 if (s.creationTime() > rightTime) {
145 rightTime = s.creationTime();
146 }
147 }
148 if (rightTime != leftTime) {
149 return leftTime > rightTime;
150 }
151
152 // as final resort we compare the fingerprints
153 return strcmp(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
154 }
155
156protected:
157 QVariant data(const QModelIndex &index, int role) const override
158 {
159 if (!index.isValid()) {
160 return QVariant();
161 }
162
163 const auto userId = QSortFilterProxyModel::data(index, KeyList::UserIDRole).value<GpgME::UserID>();
164 Q_ASSERT(!userId.isNull());
165 if (userId.isNull()) {
166 return QVariant();
167 }
168
169 switch (role) {
170 case Qt::DisplayRole:
172 const auto nameAndEmail = formatUserID(userId);
173 if (Kleo::KeyCache::instance()->pgpOnly()) {
174 return i18nc("Name <email> (validity, created: date)",
175 "%1 (%2, created: %3)",
176 nameAndEmail,
177 Kleo::Formatting::complianceStringShort(userId),
178 Kleo::Formatting::creationDateString(userId.parent()));
179 } else {
180 return i18nc("Name <email> (validity, type, created: date)",
181 "%1 (%2, %3, created: %4)",
182 nameAndEmail,
183 Kleo::Formatting::complianceStringShort(userId),
184 Formatting::displayName(userId.parent().protocol()),
185 Kleo::Formatting::creationDateString(userId.parent()));
186 }
187 }
188 case Qt::ToolTipRole: {
189 using namespace Kleo::Formatting;
190 return Kleo::Formatting::toolTip(userId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
191 }
192 case Qt::DecorationRole: {
193 return mIconProvider.icon(userId.parent());
194 }
195 case Qt::FontRole: {
196 return KeyFilterManager::instance()->font(userId.parent(), QFont());
197 }
198 default:
199 return QSortFilterProxyModel::data(index, role);
200 }
201 }
202
203private:
204 Formatting::IconProvider mIconProvider;
205};
206
207class CustomItemsProxyModel : public QSortFilterProxyModel
208{
209 Q_OBJECT
210
211private:
212 struct CustomItem {
213 QIcon icon;
214 QString text;
215 QVariant data;
216 QString toolTip;
217 };
218
219public:
220 CustomItemsProxyModel(QObject *parent = nullptr)
221 : QSortFilterProxyModel(parent)
222 {
223 }
224
225 ~CustomItemsProxyModel() override
226 {
227 qDeleteAll(mFrontItems);
228 qDeleteAll(mBackItems);
229 }
230
231 bool isCustomItem(const int row) const
232 {
233 return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount();
234 }
235
236 void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
237 {
238 beginInsertRows(QModelIndex(), 0, 0);
239 mFrontItems.push_front(new CustomItem{icon, text, data, toolTip});
240 endInsertRows();
241 }
242
243 void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
244 {
245 beginInsertRows(QModelIndex(), rowCount(), rowCount());
246 mBackItems.push_back(new CustomItem{icon, text, data, toolTip});
247 endInsertRows();
248 }
249
250 void removeCustomItem(const QVariant &data)
251 {
252 for (int i = 0; i < mFrontItems.count(); ++i) {
253 if (mFrontItems[i]->data == data) {
254 beginRemoveRows(QModelIndex(), i, i);
255 delete mFrontItems.takeAt(i);
256 endRemoveRows();
257 return;
258 }
259 }
260 for (int i = 0; i < mBackItems.count(); ++i) {
261 if (mBackItems[i]->data == data) {
262 const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i;
263 beginRemoveRows(QModelIndex(), index, index);
264 delete mBackItems.takeAt(i);
265 endRemoveRows();
266 return;
267 }
268 }
269 }
270
271 int rowCount(const QModelIndex &parent = QModelIndex()) const override
272 {
273 return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count();
274 }
275
276 int columnCount(const QModelIndex &parent = QModelIndex()) const override
277 {
278 Q_UNUSED(parent)
279 // pretend that there is only one column to workaround a bug in
280 // QAccessibleTable which provides the accessibility interface for the
281 // pop-up of the combo box
282 return 1;
283 }
284
285 QModelIndex mapToSource(const QModelIndex &index) const override
286 {
287 if (!index.isValid()) {
288 return {};
289 }
290 if (!isCustomItem(index.row())) {
291 const int sourceRow = index.row() - mFrontItems.count();
292 return QSortFilterProxyModel::mapToSource(createIndex(sourceRow, index.column(), index.internalPointer()));
293 }
294 return {};
295 }
296
297 QModelIndex mapFromSource(const QModelIndex &source_index) const override
298 {
299 const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
300 return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer());
301 }
302
303 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
304 {
305 if (row < 0 || row >= rowCount()) {
306 return {};
307 }
308 if (row < mFrontItems.count()) {
309 return createIndex(row, column, mFrontItems[row]);
310 } else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) {
311 return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]);
312 } else {
313 const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent);
314 return createIndex(row, column, mi.internalPointer());
315 }
316 }
317
318 Qt::ItemFlags flags(const QModelIndex &index) const override
319 {
320 Q_UNUSED(index)
322 }
323
324 QModelIndex parent(const QModelIndex &) const override
325 {
326 // Flat list
327 return {};
328 }
329
330 QVariant data(const QModelIndex &index, int role) const override
331 {
332 if (!index.isValid()) {
333 return QVariant();
334 }
335
336 if (isCustomItem(index.row())) {
337 Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
338 auto ci = static_cast<CustomItem *>(index.internalPointer());
339 switch (role) {
340 case Qt::DisplayRole:
341 return ci->text;
343 return ci->icon;
344 case Qt::UserRole:
345 case KeyList::UserIDRole:
346 return ci->data;
347 case Qt::ToolTipRole:
348 return ci->toolTip;
349 default:
350 return QVariant();
351 }
352 }
353
354 return QSortFilterProxyModel::data(index, role);
355 }
356
357private:
358 QList<CustomItem *> mFrontItems;
359 QList<CustomItem *> mBackItems;
360};
361
362} // anonymous namespace
363
364namespace Kleo
365{
366class UserIDSelectionComboPrivate
367{
368public:
369 UserIDSelectionComboPrivate(UserIDSelectionCombo *parent, bool secretOnly_, KeyUsage::Flags usage)
370 : wasEnabled(true)
371 , secretOnly{secretOnly_}
372 , usageFlags{usage}
373 , q{parent}
374 {
375 }
376
377 /* Selects the first key with a UID addrSpec that matches
378 * the mPerfectMatchMbox variable.
379 *
380 * The idea here is that if there are keys like:
381 *
382 * tom-store@abc.com
383 * susi-store@abc.com
384 * store@abc.com
385 *
386 * And the user wants to send a mail to "store@abc.com"
387 * the filter should still show tom and susi (because they
388 * both are part of store) but the key for "store" should
389 * be preselected.
390 *
391 * Returns true if one was selected. False otherwise. */
392 bool selectPerfectIdMatch() const
393 {
394 if (mPerfectMatchMbox.isEmpty()) {
395 return false;
396 }
397
398 for (int i = 0; i < proxyModel->rowCount(); ++i) {
399 const auto idx = proxyModel->index(i, 0, QModelIndex());
400 const auto userID = idx.data(KeyList::UserIDRole).value<GpgME::UserID>();
401 if (userID.isNull()) {
402 // WTF?
403 continue;
404 }
405 if (QString::fromStdString(userID.addrSpec()) == mPerfectMatchMbox) {
406 combo->setCurrentIndex(i);
407 return true;
408 }
409 }
410 return false;
411 }
412
413 /* Updates the current key with the default key if the key matches
414 * the current key filter. */
415 void updateWithDefaultKey()
416 {
417 GpgME::Protocol filterProto = GpgME::UnknownProtocol;
418
419 const auto filter = dynamic_cast<const DefaultKeyFilter *>(sortFilterProxy->keyFilter().get());
420 if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) {
421 filterProto = GpgME::OpenPGP;
422 } else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
423 filterProto = GpgME::CMS;
424 }
425
426 QString defaultKey = defaultKeys.value(filterProto);
427 if (defaultKey.isEmpty()) {
428 // Fallback to unknown protocol
429 defaultKey = defaultKeys.value(GpgME::UnknownProtocol);
430 }
431 // make sure that the default key is not filtered out unless it has the wrong protocol
432 if (filterProto == GpgME::UnknownProtocol) {
433 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
434 } else {
435 const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData());
436 if (!key.isNull() && key.protocol() == filterProto) {
437 sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
438 } else {
439 sortFilterProxy->setAlwaysAcceptedKey({});
440 }
441 }
442 q->setCurrentKey(defaultKey);
443 }
444
445 void storeCurrentSelectionBeforeModelChange()
446 {
447 userIDBeforeModelChange = q->currentUserID();
448 customItemBeforeModelChange = combo->currentData();
449 }
450
451 void restoreCurrentSelectionAfterModelChange()
452 {
453 if (!userIDBeforeModelChange.isNull()) {
454 q->setCurrentUserID(userIDBeforeModelChange);
455 } else if (customItemBeforeModelChange.isValid()) {
456 const auto index = combo->findData(customItemBeforeModelChange);
457 if (index != -1) {
458 combo->setCurrentIndex(index);
459 } else {
460 updateWithDefaultKey();
461 }
462 }
463 }
464
465 Kleo::AbstractKeyListModel *model = nullptr;
466 UserIDProxyModel *userIdProxy = nullptr;
467 SortFilterProxyModel *sortFilterProxy = nullptr;
468 SortAndFormatCertificatesProxyModel *sortAndFormatProxy = nullptr;
469 CustomItemsProxyModel *proxyModel = nullptr;
470 QComboBox *combo = nullptr;
471 QToolButton *button = nullptr;
472 std::shared_ptr<Kleo::KeyCache> cache;
474 bool wasEnabled = false;
475 bool useWasEnabled = false;
476 bool secretOnly = false;
477 bool initialKeyListingDone = false;
478 QString mPerfectMatchMbox;
479 GpgME::UserID userIDBeforeModelChange;
480 QVariant customItemBeforeModelChange;
481 KeyUsage::Flags usageFlags;
482
483private:
484 UserIDSelectionCombo *const q;
485};
486
487}
488
489using namespace Kleo;
490
491UserIDSelectionCombo::UserIDSelectionCombo(QWidget *parent)
492 : UserIDSelectionCombo(true, KeyUsage::None, parent)
493{
494}
495
496UserIDSelectionCombo::UserIDSelectionCombo(bool secretOnly, QWidget *parent)
497 : UserIDSelectionCombo(secretOnly, KeyUsage::None, parent)
498{
499}
500
501UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flags usage, QWidget *parent)
502 : UserIDSelectionCombo{false, usage, parent}
503{
504}
505
506UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flag usage, QWidget *parent)
507 : UserIDSelectionCombo{false, usage, parent}
508{
509}
510
511UserIDSelectionCombo::UserIDSelectionCombo(bool secretOnly, KeyUsage::Flags usage, QWidget *parent)
512 : QWidget(parent)
513 , d(new UserIDSelectionComboPrivate(this, secretOnly, usage))
514{
515 // set a non-empty string as accessible description to prevent screen readers
516 // from reading the tool tip which isn't meant for screen readers
517 setAccessibleDescription(QStringLiteral(" "));
518 d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this);
519
520 d->userIdProxy = new UserIDProxyModel(this);
521 d->userIdProxy->setSourceModel(d->model);
522
523 d->sortFilterProxy = new SortFilterProxyModel(this);
524 d->sortFilterProxy->setSourceModel(d->userIdProxy);
525
526 d->sortAndFormatProxy = new SortAndFormatCertificatesProxyModel{usage, this};
527 d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
528 // initialize dynamic sorting
529 d->sortAndFormatProxy->sort(0);
530
531 d->proxyModel = new CustomItemsProxyModel{this};
532 d->proxyModel->setSourceModel(d->sortAndFormatProxy);
533
534 auto layout = new QHBoxLayout(this);
535 layout->setContentsMargins({});
536
537 d->combo = new QComboBox(parent);
538 layout->addWidget(d->combo);
539
540 d->button = new QToolButton(parent);
541 d->button->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
542 d->button->setToolTip(i18nc("@info:tooltip", "Show certificate list"));
543 d->button->setAccessibleName(i18n("Show certificate list"));
544 layout->addWidget(d->button);
545
546 connect(d->button, &QToolButton::clicked, this, &UserIDSelectionCombo::certificateSelectionRequested);
547
548 d->combo->setModel(d->proxyModel);
549 connect(d->combo, &QComboBox::currentIndexChanged, this, [this](int row) {
550 if (row >= 0 && row < d->proxyModel->rowCount()) {
551 if (d->proxyModel->isCustomItem(row)) {
552 Q_EMIT customItemSelected(d->combo->currentData(Qt::UserRole));
553 } else {
554 Q_EMIT currentKeyChanged(currentKey());
555 }
556 }
557 });
558
559 d->cache = Kleo::KeyCache::mutableInstance();
560
561 connect(d->combo->model(), &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() {
562 d->storeCurrentSelectionBeforeModelChange();
563 });
564 connect(d->combo->model(), &QAbstractItemModel::rowsInserted, this, [this]() {
565 d->restoreCurrentSelectionAfterModelChange();
566 });
567 connect(d->combo->model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this]() {
568 d->storeCurrentSelectionBeforeModelChange();
569 });
570 connect(d->combo->model(), &QAbstractItemModel::rowsRemoved, this, [this]() {
571 d->restoreCurrentSelectionAfterModelChange();
572 });
573 connect(d->combo->model(), &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
574 d->storeCurrentSelectionBeforeModelChange();
575 });
576 connect(d->combo->model(), &QAbstractItemModel::modelReset, this, [this]() {
577 d->restoreCurrentSelectionAfterModelChange();
578 });
579
580 QTimer::singleShot(0, this, &UserIDSelectionCombo::init);
581}
582
583UserIDSelectionCombo::~UserIDSelectionCombo() = default;
584
585void UserIDSelectionCombo::init()
586{
587 connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() {
588 // Set useKeyCache ensures that the cache is populated
589 // so this can be a blocking call if the cache is not initialized
590 if (!d->initialKeyListingDone) {
591 d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
592 }
593 d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys"));
594
595 // We use the useWasEnabled state variable to decide if we should
596 // change the enable / disable state based on the keylist done signal.
597 // If we triggered the refresh useWasEnabled is true and we want to
598 // enable / disable again after our refresh, as the refresh disabled it.
599 //
600 // But if a keyListingDone signal comes from just a generic refresh
601 // triggered by someone else we don't want to change the enable / disable
602 // state.
603 if (d->useWasEnabled) {
604 setEnabled(d->wasEnabled);
605 d->useWasEnabled = false;
606 }
607 Q_EMIT keyListingFinished();
608 });
609
610 connect(this, &UserIDSelectionCombo::keyListingFinished, this, [this]() {
611 if (!d->initialKeyListingDone) {
612 d->updateWithDefaultKey();
613 d->initialKeyListingDone = true;
614 }
615 });
616
617 if (!d->cache->initialized()) {
618 refreshKeys();
619 } else {
620 d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
621 Q_EMIT keyListingFinished();
622 }
623
624 connect(d->combo, &QComboBox::currentIndexChanged, this, [this]() {
625 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
626 });
627}
628
629void UserIDSelectionCombo::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
630{
631 d->sortFilterProxy->setKeyFilter(kf);
632 d->updateWithDefaultKey();
633}
634
635std::shared_ptr<const KeyFilter> UserIDSelectionCombo::keyFilter() const
636{
637 return d->sortFilterProxy->keyFilter();
638}
639
640void UserIDSelectionCombo::setIdFilter(const QString &id)
641{
642 d->sortFilterProxy->setFilterRegularExpression(id);
643 d->mPerfectMatchMbox = id;
644 d->updateWithDefaultKey();
645}
646
647QString UserIDSelectionCombo::idFilter() const
648{
649 return d->sortFilterProxy->filterRegularExpression().pattern();
650}
651
652GpgME::Key Kleo::UserIDSelectionCombo::currentKey() const
653{
654 return d->combo->currentData(KeyList::KeyRole).value<GpgME::Key>();
655}
656
657void Kleo::UserIDSelectionCombo::setCurrentKey(const GpgME::Key &key)
658{
659 const int idx = d->combo->findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly);
660 if (idx > -1) {
661 d->combo->setCurrentIndex(idx);
662 } else if (!d->selectPerfectIdMatch()) {
663 d->updateWithDefaultKey();
664 }
665 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
666}
667
668void Kleo::UserIDSelectionCombo::setCurrentKey(const QString &fingerprint)
669{
670 const auto cur = currentKey();
671 if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1StringView(cur.primaryFingerprint())) {
672 // already set; still emit a changed signal because the current key may
673 // have become the item at the current index by changes in the underlying model
674 Q_EMIT currentKeyChanged(cur);
675 return;
676 }
677 const int idx = d->combo->findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly);
678 if (idx > -1) {
679 d->combo->setCurrentIndex(idx);
680 } else if (!d->selectPerfectIdMatch()) {
681 d->combo->setCurrentIndex(0);
682 }
683 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
684}
685
686GpgME::UserID Kleo::UserIDSelectionCombo::currentUserID() const
687{
688 return d->combo->currentData(KeyList::UserIDRole).value<GpgME::UserID>();
689}
690
691void Kleo::UserIDSelectionCombo::setCurrentUserID(const GpgME::UserID &userID)
692{
693 for (auto i = 0; i < d->combo->count(); i++) {
694 const auto &other = d->combo->itemData(i, KeyList::UserIDRole).value<GpgME::UserID>();
695 if (!qstrcmp(userID.id(), other.id()) && !qstrcmp(userID.parent().primaryFingerprint(), other.parent().primaryFingerprint())) {
696 d->combo->setCurrentIndex(i);
697 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
698 return;
699 }
700 }
701 if (!d->selectPerfectIdMatch()) {
702 d->updateWithDefaultKey();
703 setToolTip(d->combo->currentData(Qt::ToolTipRole).toString());
704 }
705}
706
707void UserIDSelectionCombo::refreshKeys()
708{
709 d->wasEnabled = isEnabled();
710 d->useWasEnabled = true;
711 setEnabled(false);
712 const bool wasBlocked = blockSignals(true);
713 prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys"));
714 d->combo->setCurrentIndex(0);
715 blockSignals(wasBlocked);
716 d->cache->startKeyListing();
717}
718
719void UserIDSelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
720{
721 d->proxyModel->appendItem(icon, text, data, toolTip);
722}
723
724void UserIDSelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
725{
726 appendCustomItem(icon, text, data, QString());
727}
728
729void UserIDSelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
730{
731 d->proxyModel->prependItem(icon, text, data, toolTip);
732}
733
734void UserIDSelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
735{
736 prependCustomItem(icon, text, data, QString());
737}
738
739void UserIDSelectionCombo::removeCustomItem(const QVariant &data)
740{
741 d->proxyModel->removeCustomItem(data);
742}
743
744void Kleo::UserIDSelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto)
745{
746 d->defaultKeys.insert(proto, fingerprint);
747 d->updateWithDefaultKey();
748}
749
750void Kleo::UserIDSelectionCombo::setDefaultKey(const QString &fingerprint)
751{
752 setDefaultKey(fingerprint, GpgME::UnknownProtocol);
753}
754
755QString Kleo::UserIDSelectionCombo::defaultKey(GpgME::Protocol proto) const
756{
757 return d->defaultKeys.value(proto);
758}
759
760QString Kleo::UserIDSelectionCombo::defaultKey() const
761{
762 return defaultKey(GpgME::UnknownProtocol);
763}
764
765QComboBox *Kleo::UserIDSelectionCombo::combo() const
766{
767 return d->combo;
768}
769
770int Kleo::UserIDSelectionCombo::findUserId(const GpgME::UserID &userId) const
771{
772 for (int i = 0; i < combo()->model()->rowCount(); i++) {
773 if (Kleo::userIDsAreEqual(userId, combo()->model()->index(i, 0).data(KeyList::UserIDRole).value<GpgME::UserID>())) {
774 return i;
775 }
776 }
777 return -1;
778}
779
780#include "useridselectioncombo.moc"
781
782#include "moc_useridselectioncombo.cpp"
DN parser and reorderer.
Definition dn.h:27
Default implementation of key filter class.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
void clicked(bool checked)
void modelAboutToBeReset()
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
const char * constData() const const
void setCurrentIndex(int index)
void currentIndexChanged(int index)
int findData(const QVariant &data, int role, Qt::MatchFlags flags) const const
QIcon fromTheme(const QString &name)
T value(const Key &key, const T &defaultValue) const const
int column() const const
void * internalPointer() const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual QVariant data(const QModelIndex &index, int role) const const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual int rowCount(const QModelIndex &parent) const const override
QChar * data()
QString fromLatin1(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
int localeAwareCompare(QStringView s1, QStringView s2)
QByteArray toLatin1() const const
DisplayRole
typedef ItemFlags
MatchExactly
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isValid() const const
T value() const const
bool isEnabled() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:11:58 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.