Libkleo

keylistmodel.cpp
1/* -*- mode: c++; c-basic-offset:4 -*-
2 models/keylistmodel.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
6 SPDX-FileCopyrightText: 2021 g10 Code GmbH
7 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11
12#include <config-libkleo.h>
13
14#include "keylistmodel.h"
15
16#include "keycache.h"
17
18#include <libkleo/algorithm.h>
19#include <libkleo/formatting.h>
20#include <libkleo/keyfilter.h>
21#include <libkleo/keyfiltermanager.h>
22#include <libkleo/keyhelpers.h>
23#include <libkleo/predicates.h>
24#include <libkleo/systeminfo.h>
25
26#include <KLocalizedString>
27
28#ifdef KLEO_MODEL_TEST
29#include <QAbstractItemModelTester>
30#endif
31#include <QColor>
32#include <QDate>
33#include <QFont>
34#include <QHash>
35#include <QIcon>
36#include <QMimeData>
37
38#include <gpgme++/key.h>
39
40#ifndef Q_MOC_RUN // QTBUG-22829
41#include <boost/graph/adjacency_list.hpp>
42#include <boost/graph/topological_sort.hpp>
43#endif
44
45#include <algorithm>
46#include <iterator>
47#include <map>
48#include <set>
49
50using namespace GpgME;
51using namespace Kleo;
52using namespace Kleo::KeyList;
53
54#if !UNITY_BUILD
55Q_DECLARE_METATYPE(GpgME::Key)
56Q_DECLARE_METATYPE(KeyGroup)
57#endif
58
59class AbstractKeyListModel::Private
60{
61 AbstractKeyListModel *const q;
62
63public:
64 explicit Private(AbstractKeyListModel *qq);
65
66 void updateFromKeyCache();
67
68 QString getEMail(const Key &key) const;
69
70public:
71 int m_toolTipOptions = Formatting::Validity;
72 mutable QHash<const char *, QString> prettyEMailCache;
73 mutable QHash<const char *, QVariant> remarksCache;
74 bool m_useKeyCache = false;
75 bool m_modelResetInProgress = false;
76 KeyList::Options m_keyListOptions = AllKeys;
77 std::vector<GpgME::Key> m_remarkKeys;
78 std::shared_ptr<DragHandler> m_dragHandler;
79 std::vector<Key::Origin> extraOrigins;
80};
81
82AbstractKeyListModel::Private::Private(Kleo::AbstractKeyListModel *qq)
83 : q(qq)
84{
85}
86
87void AbstractKeyListModel::Private::updateFromKeyCache()
88{
89 if (m_useKeyCache) {
90 const bool inReset = q->modelResetInProgress();
91 if (!inReset) {
92 q->beginResetModel();
93 }
94 q->setKeys(m_keyListOptions == SecretKeysOnly ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys());
95 if (m_keyListOptions == IncludeGroups) {
96 q->setGroups(KeyCache::instance()->groups());
97 }
98 if (!inReset) {
99 q->endResetModel();
100 }
101 }
102}
103
104QString AbstractKeyListModel::Private::getEMail(const Key &key) const
105{
106 QString email;
107 if (const auto fpr = key.primaryFingerprint()) {
108 const auto it = prettyEMailCache.constFind(fpr);
109 if (it != prettyEMailCache.constEnd()) {
110 email = *it;
111 } else {
112 email = Formatting::prettyEMail(key);
113 prettyEMailCache[fpr] = email;
114 }
115 }
116 return email;
117}
118
119AbstractKeyListModel::AbstractKeyListModel(QObject *p)
121 , KeyListModelInterface()
122 , d(new Private(this))
123{
124 connect(this, &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
125 d->m_modelResetInProgress = true;
126 });
127 connect(this, &QAbstractItemModel::modelReset, this, [this]() {
128 d->m_modelResetInProgress = false;
129 });
130}
131
132AbstractKeyListModel::~AbstractKeyListModel()
133{
134}
135
136void AbstractKeyListModel::setToolTipOptions(int opts)
137{
138 d->m_toolTipOptions = opts;
139}
140
141int AbstractKeyListModel::toolTipOptions() const
142{
143 return d->m_toolTipOptions;
144}
145
146void AbstractKeyListModel::setRemarkKeys(const std::vector<GpgME::Key> &keys)
147{
148 d->m_remarkKeys = keys;
149}
150
151std::vector<GpgME::Key> AbstractKeyListModel::remarkKeys() const
152{
153 return d->m_remarkKeys;
154}
155
156Key AbstractKeyListModel::key(const QModelIndex &idx) const
157{
158 Key key = Key::null;
159 if (idx.isValid()) {
160 key = doMapToKey(idx);
161 }
162 return key;
163}
164
165std::vector<Key> AbstractKeyListModel::keys(const QList<QModelIndex> &indexes) const
166{
167 std::vector<Key> result;
168 result.reserve(indexes.size());
169 std::transform(indexes.begin(), //
170 indexes.end(),
171 std::back_inserter(result),
172 [this](const QModelIndex &idx) {
173 return this->key(idx);
174 });
175 result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&GpgME::Key::isNull)), result.end());
176 _detail::remove_duplicates_by_fpr(result);
177 return result;
178}
179
180KeyGroup AbstractKeyListModel::group(const QModelIndex &idx) const
181{
182 if (idx.isValid()) {
183 return doMapToGroup(idx);
184 } else {
185 return KeyGroup();
186 }
187}
188
189QModelIndex AbstractKeyListModel::index(const Key &key) const
190{
191 return index(key, 0);
192}
193
194QModelIndex AbstractKeyListModel::index(const Key &key, int col) const
195{
196 if (key.isNull() || col < 0 || col >= NumColumns) {
197 return {};
198 } else {
199 return doMapFromKey(key, col);
200 }
201}
202
203QList<QModelIndex> AbstractKeyListModel::indexes(const std::vector<Key> &keys) const
204{
205 QList<QModelIndex> result;
206 result.reserve(keys.size());
207 std::transform(keys.begin(), //
208 keys.end(),
209 std::back_inserter(result),
210 [this](const Key &key) {
211 return this->index(key);
212 });
213 return result;
214}
215
216QModelIndex AbstractKeyListModel::index(const KeyGroup &group) const
217{
218 return index(group, 0);
219}
220
221QModelIndex AbstractKeyListModel::index(const KeyGroup &group, int col) const
222{
223 if (group.isNull() || col < 0 || col >= NumColumns) {
224 return {};
225 } else {
226 return doMapFromGroup(group, col);
227 }
228}
229
230void AbstractKeyListModel::setKeys(const std::vector<Key> &keys, const std::vector<Key::Origin> &extraOrigins)
231{
232 const bool inReset = modelResetInProgress();
233 if (!inReset) {
235 }
236 clear(Keys);
237 addKeys(keys);
238 d->extraOrigins = extraOrigins;
239 if (!inReset) {
241 }
242}
243
244QModelIndex AbstractKeyListModel::addKey(const Key &key)
245{
246 const std::vector<Key> vec(1, key);
247 const QList<QModelIndex> l = doAddKeys(vec);
248 return l.empty() ? QModelIndex() : l.front();
249}
250
251void AbstractKeyListModel::removeKey(const Key &key)
252{
253 if (key.isNull()) {
254 return;
255 }
256 doRemoveKey(key);
257 d->prettyEMailCache.remove(key.primaryFingerprint());
258 d->remarksCache.remove(key.primaryFingerprint());
259}
260
261QList<QModelIndex> AbstractKeyListModel::addKeys(const std::vector<Key> &keys)
262{
263 std::vector<Key> sorted;
264 sorted.reserve(keys.size());
265 std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), std::mem_fn(&Key::isNull));
266 std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
267 return doAddKeys(sorted);
268}
269
270void AbstractKeyListModel::setGroups(const std::vector<KeyGroup> &groups)
271{
272 const bool inReset = modelResetInProgress();
273 if (!inReset) {
275 }
276 clear(Groups);
277 doSetGroups(groups);
278 if (!inReset) {
280 }
281}
282
283QModelIndex AbstractKeyListModel::addGroup(const KeyGroup &group)
284{
285 if (group.isNull()) {
286 return QModelIndex();
287 }
288 return doAddGroup(group);
289}
290
291bool AbstractKeyListModel::removeGroup(const KeyGroup &group)
292{
293 if (group.isNull()) {
294 return false;
295 }
296 return doRemoveGroup(group);
297}
298
299void AbstractKeyListModel::clear(ItemTypes types)
300{
301 const bool inReset = modelResetInProgress();
302 if (!inReset) {
304 }
305 doClear(types);
306 if (types & Keys) {
307 d->prettyEMailCache.clear();
308 d->remarksCache.clear();
309 }
310 if (!inReset) {
312 }
313}
314
315int AbstractKeyListModel::columnCount(const QModelIndex &) const
316{
317 return NumColumns;
318}
319
320QVariant AbstractKeyListModel::headerData(int section, Qt::Orientation o, int role) const
321{
322 if (o == Qt::Horizontal) {
323 if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) {
324 switch (section) {
325 case PrettyName:
326 return i18nc("@title:column", "Name");
327 case PrettyEMail:
328 return i18nc("@title:column", "E-Mail");
329 case Validity:
330 return i18nc("@title:column", "Status");
331 case ValidFrom:
332 return i18nc("@title:column", "Valid From");
333 case ValidUntil:
334 return i18nc("@title:column", "Valid Until");
335 case TechnicalDetails:
336 return i18nc("@title:column", "Protocol");
337 case KeyID:
338 return i18nc("@title:column", "Key ID");
339 case Fingerprint:
340 return i18nc("@title:column", "Fingerprint");
341 case Issuer:
342 return i18nc("@title:column", "Issuer");
343 case SerialNumber:
344 return i18nc("@title:column", "Serial Number");
345 case Origin:
346 return i18nc("@title:column", "Origin");
347 case LastUpdate:
348 return i18nc("@title:column", "Last Update");
349 case OwnerTrust:
350 return i18nc("@title:column", "Certification Trust");
351 case Remarks:
352 return i18nc("@title:column", "Tags");
353 case Algorithm:
354 return i18nc("@title:column", "Algorithm");
355 case Keygrip:
356 return i18nc("@title:column", "Keygrip");
357 case NumColumns:;
358 }
359 }
360 }
361 return QVariant();
362}
363
364static QVariant returnIfValid(const QColor &t)
365{
366 if (t.isValid()) {
367 return t;
368 } else {
369 return QVariant();
370 }
371}
372
373static QVariant returnIfValid(const QIcon &t)
374{
375 if (!t.isNull()) {
376 return t;
377 } else {
378 return QVariant();
379 }
380}
381
382QVariant AbstractKeyListModel::data(const QModelIndex &index, int role) const
383{
384 const Key key = this->key(index);
385 if (!key.isNull()) {
386 return data(key, index.row(), index.column(), role);
387 }
388
389 const KeyGroup group = this->group(index);
390 if (!group.isNull()) {
391 return data(group, index.column(), role);
392 }
393
394 return QVariant();
395}
396
397QVariant AbstractKeyListModel::data(const Key &key, int row, int column, int role) const
398{
399 if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole || role == ClipboardRole) {
400 switch (column) {
401 case PrettyName: {
402 const auto name = Formatting::prettyName(key);
403 if (role == Qt::AccessibleTextRole) {
404 return name.isEmpty() ? i18nc("text for screen readers for an empty name", "no name") : name;
405 }
406 return name;
407 }
408 case PrettyEMail: {
409 const auto email = d->getEMail(key);
410 if (role == Qt::AccessibleTextRole) {
411 return email.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : email;
412 }
413 return email;
414 }
415 case Validity:
416 return Formatting::complianceStringShort(key);
417 case ValidFrom:
418 if (role == Qt::EditRole) {
419 return Formatting::creationDate(key);
420 } else if (role == Qt::AccessibleTextRole) {
421 return Formatting::accessibleCreationDate(key);
422 } else {
423 return Formatting::creationDateString(key);
424 }
425 case ValidUntil:
426 if (role == Qt::EditRole) {
427 return Formatting::expirationDate(key);
428 } else if (role == Qt::AccessibleTextRole) {
429 return Formatting::accessibleExpirationDate(key);
430 } else {
431 return Formatting::expirationDateString(key);
432 }
433 case TechnicalDetails:
434 return Formatting::type(key);
435 case KeyID:
436 if (role == Qt::AccessibleTextRole) {
437 return Formatting::accessibleHexID(key.keyID());
438 } else if (role == ClipboardRole) {
439 return QString::fromLatin1(key.keyID());
440 } else {
441 return Formatting::prettyID(key.keyID());
442 }
443 case Summary:
444 return Formatting::summaryLine(key);
445 case Fingerprint:
446 if (role == Qt::AccessibleTextRole) {
447 return Formatting::accessibleHexID(key.primaryFingerprint());
448 } else if (role == ClipboardRole) {
449 return QString::fromLatin1(key.primaryFingerprint());
450 } else {
451 return Formatting::prettyID(key.primaryFingerprint());
452 }
453 case Issuer:
454 return QString::fromUtf8(key.issuerName());
455 case Origin:
456 if (key.origin() == Key::OriginUnknown && (int)d->extraOrigins.size() > row) {
457 return Formatting::origin(d->extraOrigins[row]);
458 }
459 return Formatting::origin(key.origin());
460 case LastUpdate:
461 if (role == Qt::AccessibleTextRole) {
462 return Formatting::accessibleDate(key.lastUpdate());
463 } else {
464 return Formatting::dateString(key.lastUpdate());
465 }
466 case SerialNumber:
467 return QString::fromUtf8(key.issuerSerial());
468 case OwnerTrust:
469 return Formatting::ownerTrustShort(key.ownerTrust());
470 case Remarks: {
471 const char *const fpr = key.primaryFingerprint();
472 if (fpr && key.protocol() == GpgME::OpenPGP && key.numUserIDs() && d->m_remarkKeys.size()) {
473 if (!(key.keyListMode() & GpgME::SignatureNotations)) {
474 return i18n("Loading...");
475 }
476 const QHash<const char *, QVariant>::const_iterator it = d->remarksCache.constFind(fpr);
477 if (it != d->remarksCache.constEnd()) {
478 return *it;
479 } else {
480 GpgME::Error err;
481 const auto remarks = key.userID(0).remarks(d->m_remarkKeys, err);
482 if (remarks.size() == 1) {
483 const auto remark = QString::fromStdString(remarks[0]);
484 return d->remarksCache[fpr] = remark;
485 } else {
486 QStringList remarkList;
487 remarkList.reserve(remarks.size());
488 for (const auto &rem : remarks) {
489 remarkList << QString::fromStdString(rem);
490 }
491 const auto remark = remarkList.join(QStringLiteral("; "));
492 return d->remarksCache[fpr] = remark;
493 }
494 }
495 } else {
496 return QVariant();
497 }
498 }
499 return QVariant();
500 case Algorithm:
501 return Formatting::prettyAlgorithmName(key.subkey(0).algoName());
502 case Keygrip:
503 if (role == Qt::AccessibleTextRole) {
504 return Formatting::accessibleHexID(key.subkey(0).keyGrip());
505 } else {
506 return QString::fromLatin1(key.subkey(0).keyGrip());
507 }
508 case NumColumns:
509 break;
510 }
511 } else if (role == Qt::ToolTipRole) {
512 return Formatting::toolTip(key, toolTipOptions());
513 } else if (role == Qt::FontRole) {
514 return KeyFilterManager::instance()->font(key, (column == KeyID || column == Fingerprint) ? QFont(QStringLiteral("monospace")) : QFont());
515 } else if (role == Qt::DecorationRole) {
516 return column == Icon ? returnIfValid(KeyFilterManager::instance()->icon(key)) : QVariant();
517 } else if (role == Qt::BackgroundRole) {
518 if (!SystemInfo::isHighContrastModeActive()) {
519 return returnIfValid(KeyFilterManager::instance()->bgColor(key));
520 }
521 } else if (role == Qt::ForegroundRole) {
522 if (!SystemInfo::isHighContrastModeActive()) {
523 return returnIfValid(KeyFilterManager::instance()->fgColor(key));
524 }
525 } else if (role == FingerprintRole) {
526 return QString::fromLatin1(key.primaryFingerprint());
527 } else if (role == KeyRole) {
528 return QVariant::fromValue(key);
529 }
530 return QVariant();
531}
532
533QVariant AbstractKeyListModel::data(const KeyGroup &group, int column, int role) const
534{
535 if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole) {
536 switch (column) {
537 case PrettyName:
538 return group.name();
539 case Validity:
540 return Formatting::complianceStringShort(group);
541 case TechnicalDetails:
542 return Formatting::type(group);
543 case Summary:
544 return Formatting::summaryLine(group); // used for filtering
545 case PrettyEMail:
546 case ValidFrom:
547 case ValidUntil:
548 case KeyID:
549 case Fingerprint:
550 case Issuer:
551 case Origin:
552 case LastUpdate:
553 case SerialNumber:
554 case OwnerTrust:
555 case Remarks:
556 if (role == Qt::AccessibleTextRole) {
557 return i18nc("text for screen readers", "not applicable");
558 }
559 break;
560 case NumColumns:
561 break;
562 }
563 } else if (role == Qt::ToolTipRole) {
564 return Formatting::toolTip(group, toolTipOptions());
565 } else if (role == Qt::FontRole) {
566 return QFont();
567 } else if (role == Qt::DecorationRole) {
568 if (column != Icon && column != Summary) {
569 return QVariant();
570 }
571 return Kleo::all_of(group.keys(),
572 [](const auto &key) {
573 return Kleo::canBeUsedForEncryption(key);
574 })
575 ? QIcon::fromTheme(QStringLiteral("group"))
576 : QIcon::fromTheme(QStringLiteral("emblem-warning"));
577 } else if (role == Qt::BackgroundRole) {
578 } else if (role == Qt::ForegroundRole) {
579 } else if (role == GroupRole) {
580 return QVariant::fromValue(group);
581 }
582 return QVariant();
583}
584
585bool AbstractKeyListModel::setData(const QModelIndex &index, const QVariant &value, int role)
586{
587 Q_UNUSED(role)
588 Q_ASSERT(value.canConvert<KeyGroup>());
589 if (value.canConvert<KeyGroup>()) {
590 const KeyGroup group = value.value<KeyGroup>();
591 return doSetGroupData(index, group);
592 }
593
594 return false;
595}
596
597bool AbstractKeyListModel::modelResetInProgress()
598{
599 return d->m_modelResetInProgress;
600}
601
602namespace
603{
604template<typename Base>
605class TableModelMixin : public Base
606{
607public:
608 explicit TableModelMixin(QObject *p = nullptr)
609 : Base(p)
610 {
611 }
612 ~TableModelMixin() override
613 {
614 }
615
616 using Base::index;
617 QModelIndex index(int row, int column, const QModelIndex &pidx = QModelIndex()) const override
618 {
619 return this->hasIndex(row, column, pidx) ? this->createIndex(row, column, nullptr) : QModelIndex();
620 }
621
622private:
623 QModelIndex parent(const QModelIndex &) const override
624 {
625 return QModelIndex();
626 }
627 bool hasChildren(const QModelIndex &pidx) const override
628 {
629 return (pidx.model() == this || !pidx.isValid()) && this->rowCount(pidx) > 0 && this->columnCount(pidx) > 0;
630 }
631};
632
633class FlatKeyListModel
634#ifndef Q_MOC_RUN
635 : public TableModelMixin<AbstractKeyListModel>
636#else
637 : public AbstractKeyListModel
638#endif
639{
640 Q_OBJECT
641public:
642 explicit FlatKeyListModel(QObject *parent = nullptr);
643 ~FlatKeyListModel() override;
644
645 int rowCount(const QModelIndex &pidx) const override
646 {
647 return pidx.isValid() ? 0 : mKeysByFingerprint.size() + mGroups.size();
648 }
649
650private:
651 Key doMapToKey(const QModelIndex &index) const override;
652 QModelIndex doMapFromKey(const Key &key, int col) const override;
653 QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) override;
654 void doRemoveKey(const Key &key) override;
655
656 KeyGroup doMapToGroup(const QModelIndex &index) const override;
657 QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override;
658 void doSetGroups(const std::vector<KeyGroup> &groups) override;
659 QModelIndex doAddGroup(const KeyGroup &group) override;
660 bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override;
661 bool doRemoveGroup(const KeyGroup &group) override;
662
663 void doClear(ItemTypes types) override
664 {
665 if (types & Keys) {
666 mKeysByFingerprint.clear();
667 }
668 if (types & Groups) {
669 mGroups.clear();
670 }
671 }
672
673 int firstGroupRow() const
674 {
675 return mKeysByFingerprint.size();
676 }
677
678 int lastGroupRow() const
679 {
680 return mKeysByFingerprint.size() + mGroups.size() - 1;
681 }
682
683 int groupIndex(const QModelIndex &index) const
684 {
685 if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) {
686 return -1;
687 }
688 return index.row() - firstGroupRow();
689 }
690
691private:
692 std::vector<Key> mKeysByFingerprint;
693 std::vector<KeyGroup> mGroups;
694};
695
696class HierarchicalKeyListModel : public AbstractKeyListModel
697{
698 Q_OBJECT
699public:
700 explicit HierarchicalKeyListModel(QObject *parent = nullptr);
701 ~HierarchicalKeyListModel() override;
702
703 int rowCount(const QModelIndex &pidx) const override;
704 using AbstractKeyListModel::index;
705 QModelIndex index(int row, int col, const QModelIndex &pidx) const override;
706 QModelIndex parent(const QModelIndex &idx) const override;
707
708 bool hasChildren(const QModelIndex &pidx) const override
709 {
710 return rowCount(pidx) > 0;
711 }
712
713private:
714 Key doMapToKey(const QModelIndex &index) const override;
715 QModelIndex doMapFromKey(const Key &key, int col) const override;
716 QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) override;
717 void doRemoveKey(const Key &key) override;
718
719 KeyGroup doMapToGroup(const QModelIndex &index) const override;
720 QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override;
721 void doSetGroups(const std::vector<KeyGroup> &groups) override;
722 QModelIndex doAddGroup(const KeyGroup &group) override;
723 bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override;
724 bool doRemoveGroup(const KeyGroup &group) override;
725
726 void doClear(ItemTypes types) override;
727
728 int firstGroupRow() const
729 {
730 return mTopLevels.size();
731 }
732
733 int lastGroupRow() const
734 {
735 return mTopLevels.size() + mGroups.size() - 1;
736 }
737
738 int groupIndex(const QModelIndex &index) const
739 {
740 if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) {
741 return -1;
742 }
743 return index.row() - firstGroupRow();
744 }
745
746private:
747 void addTopLevelKey(const Key &key);
748 void addKeyWithParent(const char *issuer_fpr, const Key &key);
749 void addKeyWithoutParent(const char *issuer_fpr, const Key &key);
750
751private:
752 typedef std::map<std::string, std::vector<Key>> Map;
753 std::vector<Key> mKeysByFingerprint; // all keys
754 Map mKeysByExistingParent, mKeysByNonExistingParent; // parent->child map
755 std::vector<Key> mTopLevels; // all roots + parent-less
756 std::vector<KeyGroup> mGroups;
757};
758
759class Issuers
760{
761 Issuers()
762 {
763 }
764
765public:
766 static Issuers *instance()
767 {
768 static auto self = std::unique_ptr<Issuers>{new Issuers{}};
769 return self.get();
770 }
771
772 const char *cleanChainID(const Key &key) const
773 {
774 const char *chainID = "";
775 if (!key.isRoot()) {
776 const char *const chid = key.chainID();
777 if (chid && mKeysWithMaskedIssuer.find(key) == std::end(mKeysWithMaskedIssuer)) {
778 chainID = chid;
779 }
780 }
781 return chainID;
782 }
783
784 void maskIssuerOfKey(const Key &key)
785 {
786 mKeysWithMaskedIssuer.insert(key);
787 }
788
789 void clear()
790 {
791 mKeysWithMaskedIssuer.clear();
792 }
793
794private:
795 std::set<Key, _detail::ByFingerprint<std::less>> mKeysWithMaskedIssuer;
796};
797
798static const char *cleanChainID(const Key &key)
799{
800 return Issuers::instance()->cleanChainID(key);
801}
802
803}
804
805FlatKeyListModel::FlatKeyListModel(QObject *p)
806 : TableModelMixin<AbstractKeyListModel>(p)
807{
808}
809
810FlatKeyListModel::~FlatKeyListModel()
811{
812}
813
814Key FlatKeyListModel::doMapToKey(const QModelIndex &idx) const
815{
816 Q_ASSERT(idx.isValid());
817 if (static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() && idx.column() < NumColumns) {
818 return mKeysByFingerprint[idx.row()];
819 } else {
820 return Key::null;
821 }
822}
823
824QModelIndex FlatKeyListModel::doMapFromKey(const Key &key, int col) const
825{
826 Q_ASSERT(!key.isNull());
827 const std::vector<Key>::const_iterator it =
828 std::lower_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
829 if (it == mKeysByFingerprint.end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
830 return {};
831 } else {
832 return createIndex(it - mKeysByFingerprint.begin(), col);
833 }
834}
835
836QList<QModelIndex> FlatKeyListModel::doAddKeys(const std::vector<Key> &keys)
837{
838 Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
839
840 if (keys.empty()) {
841 return QList<QModelIndex>();
842 }
843
844 for (auto it = keys.begin(), end = keys.end(); it != end; ++it) {
845 // find an insertion point:
846 const std::vector<Key>::iterator pos = std::upper_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), *it, _detail::ByFingerprint<std::less>());
847 const unsigned int idx = std::distance(mKeysByFingerprint.begin(), pos);
848
849 if (idx > 0 && qstrcmp(mKeysByFingerprint[idx - 1].primaryFingerprint(), it->primaryFingerprint()) == 0) {
850 // key existed before - replace with new one:
851 mKeysByFingerprint[idx - 1] = *it;
852 if (!modelResetInProgress()) {
853 Q_EMIT dataChanged(createIndex(idx - 1, 0), createIndex(idx - 1, NumColumns - 1));
854 }
855 } else {
856 // new key - insert:
857 if (!modelResetInProgress()) {
858 beginInsertRows(QModelIndex(), idx, idx);
859 }
860 mKeysByFingerprint.insert(pos, *it);
861 if (!modelResetInProgress()) {
862 endInsertRows();
863 }
864 }
865 }
866
867 return indexes(keys);
868}
869
870void FlatKeyListModel::doRemoveKey(const Key &key)
871{
872 const std::vector<Key>::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
873 if (it == mKeysByFingerprint.end()) {
874 return;
875 }
876
877 const unsigned int row = std::distance(mKeysByFingerprint.begin(), it);
878 if (!modelResetInProgress()) {
879 beginRemoveRows(QModelIndex(), row, row);
880 }
881 mKeysByFingerprint.erase(it);
882 if (!modelResetInProgress()) {
883 endRemoveRows();
884 }
885}
886
887KeyGroup FlatKeyListModel::doMapToGroup(const QModelIndex &idx) const
888{
889 Q_ASSERT(idx.isValid());
890 if (static_cast<unsigned>(idx.row()) >= mKeysByFingerprint.size() && static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() + mGroups.size()
891 && idx.column() < NumColumns) {
892 return mGroups[idx.row() - mKeysByFingerprint.size()];
893 } else {
894 return KeyGroup();
895 }
896}
897
898QModelIndex FlatKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const
899{
900 Q_ASSERT(!group.isNull());
901 const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) {
902 return g.source() == group.source() && g.id() == group.id();
903 });
904 if (it == mGroups.cend()) {
905 return QModelIndex();
906 } else {
907 return createIndex(it - mGroups.cbegin() + mKeysByFingerprint.size(), column);
908 }
909}
910
911void FlatKeyListModel::doSetGroups(const std::vector<KeyGroup> &groups)
912{
913 Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared
914 const int first = mKeysByFingerprint.size();
915 const int last = first + groups.size() - 1;
916 if (!modelResetInProgress()) {
917 beginInsertRows(QModelIndex(), first, last);
918 }
919 mGroups = groups;
920 if (!modelResetInProgress()) {
921 endInsertRows();
922 }
923}
924
925QModelIndex FlatKeyListModel::doAddGroup(const KeyGroup &group)
926{
927 const int newRow = lastGroupRow() + 1;
928 if (!modelResetInProgress()) {
929 beginInsertRows(QModelIndex(), newRow, newRow);
930 }
931 mGroups.push_back(group);
932 if (!modelResetInProgress()) {
933 endInsertRows();
934 }
935 return createIndex(newRow, 0);
936}
937
938bool FlatKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group)
939{
940 if (group.isNull()) {
941 return false;
942 }
943 const int groupIndex = this->groupIndex(index);
944 if (groupIndex == -1) {
945 return false;
946 }
947 mGroups[groupIndex] = group;
948 if (!modelResetInProgress()) {
949 Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1));
950 }
951 return true;
952}
953
954bool FlatKeyListModel::doRemoveGroup(const KeyGroup &group)
955{
956 const QModelIndex modelIndex = doMapFromGroup(group, 0);
957 if (!modelIndex.isValid()) {
958 return false;
959 }
960 const int groupIndex = this->groupIndex(modelIndex);
961 Q_ASSERT(groupIndex != -1);
962 if (groupIndex == -1) {
963 return false;
964 }
965 if (!modelResetInProgress()) {
966 beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row());
967 }
968 mGroups.erase(mGroups.begin() + groupIndex);
969 if (!modelResetInProgress()) {
970 endRemoveRows();
971 }
972 return true;
973}
974
975HierarchicalKeyListModel::HierarchicalKeyListModel(QObject *p)
976 : AbstractKeyListModel(p)
977 , mKeysByFingerprint()
978 , mKeysByExistingParent()
979 , mKeysByNonExistingParent()
980 , mTopLevels()
981{
982}
983
984HierarchicalKeyListModel::~HierarchicalKeyListModel()
985{
986}
987
988int HierarchicalKeyListModel::rowCount(const QModelIndex &pidx) const
989{
990 // toplevel item:
991 if (!pidx.isValid()) {
992 return mTopLevels.size() + mGroups.size();
993 }
994
995 if (pidx.column() != 0) {
996 return 0;
997 }
998
999 // non-toplevel item - find the number of subjects for this issuer:
1000 const Key issuer = this->key(pidx);
1001 const char *const fpr = issuer.primaryFingerprint();
1002 if (!fpr || !*fpr) {
1003 return 0;
1004 }
1005 const Map::const_iterator it = mKeysByExistingParent.find(fpr);
1006 if (it == mKeysByExistingParent.end()) {
1007 return 0;
1008 }
1009 return it->second.size();
1010}
1011
1012QModelIndex HierarchicalKeyListModel::index(int row, int col, const QModelIndex &pidx) const
1013{
1014 if (row < 0 || col < 0 || col >= NumColumns) {
1015 return {};
1016 }
1017
1018 // toplevel item:
1019 if (!pidx.isValid()) {
1020 if (static_cast<unsigned>(row) < mTopLevels.size()) {
1021 return index(mTopLevels[row], col);
1022 } else if (static_cast<unsigned>(row) < mTopLevels.size() + mGroups.size()) {
1023 return index(mGroups[row - mTopLevels.size()], col);
1024 } else {
1025 return QModelIndex();
1026 }
1027 }
1028
1029 // non-toplevel item - find the row'th subject of this key:
1030 const Key issuer = this->key(pidx);
1031 const char *const fpr = issuer.primaryFingerprint();
1032 if (!fpr || !*fpr) {
1033 return QModelIndex();
1034 }
1035 const Map::const_iterator it = mKeysByExistingParent.find(fpr);
1036 if (it == mKeysByExistingParent.end() || static_cast<unsigned>(row) >= it->second.size()) {
1037 return QModelIndex();
1038 }
1039 return index(it->second[row], col);
1040}
1041
1042QModelIndex HierarchicalKeyListModel::parent(const QModelIndex &idx) const
1043{
1044 const Key key = this->key(idx);
1045 if (key.isNull() || key.isRoot()) {
1046 return {};
1047 }
1048 const std::vector<Key>::const_iterator it =
1049 Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), cleanChainID(key), _detail::ByFingerprint<std::less>());
1050 return it != mKeysByFingerprint.end() ? index(*it) : QModelIndex();
1051}
1052
1053Key HierarchicalKeyListModel::doMapToKey(const QModelIndex &idx) const
1054{
1055 Key key = Key::null;
1056
1057 if (idx.isValid()) {
1058 const char *const issuer_fpr = static_cast<const char *>(idx.internalPointer());
1059 if (!issuer_fpr || !*issuer_fpr) {
1060 // top-level:
1061 if (static_cast<unsigned>(idx.row()) < mTopLevels.size()) {
1062 key = mTopLevels[idx.row()];
1063 }
1064 } else {
1065 // non-toplevel:
1066 const Map::const_iterator it = mKeysByExistingParent.find(issuer_fpr);
1067 if (it != mKeysByExistingParent.end() && static_cast<unsigned>(idx.row()) < it->second.size()) {
1068 key = it->second[idx.row()];
1069 }
1070 }
1071 }
1072
1073 return key;
1074}
1075
1076QModelIndex HierarchicalKeyListModel::doMapFromKey(const Key &key, int col) const
1077{
1078 if (key.isNull()) {
1079 return {};
1080 }
1081
1082 const char *issuer_fpr = cleanChainID(key);
1083
1084 // we need to look in the toplevels list,...
1085 const std::vector<Key> *v = &mTopLevels;
1086 if (issuer_fpr && *issuer_fpr) {
1087 const std::map<std::string, std::vector<Key>>::const_iterator it = mKeysByExistingParent.find(issuer_fpr);
1088 // ...unless we find an existing parent:
1089 if (it != mKeysByExistingParent.end()) {
1090 v = &it->second;
1091 } else {
1092 issuer_fpr = nullptr; // force internalPointer to zero for toplevels
1093 }
1094 }
1095
1096 const std::vector<Key>::const_iterator it = std::lower_bound(v->begin(), v->end(), key, _detail::ByFingerprint<std::less>());
1097 if (it == v->end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
1098 return QModelIndex();
1099 }
1100
1101 const unsigned int row = std::distance(v->begin(), it);
1102 return createIndex(row, col, const_cast<char * /* thanks, Trolls :/ */>(issuer_fpr));
1103}
1104
1105void HierarchicalKeyListModel::addKeyWithParent(const char *issuer_fpr, const Key &key)
1106{
1107 Q_ASSERT(issuer_fpr);
1108 Q_ASSERT(*issuer_fpr);
1109 Q_ASSERT(!key.isNull());
1110
1111 std::vector<Key> &subjects = mKeysByExistingParent[issuer_fpr];
1112
1113 // find insertion point:
1114 const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
1115 const int row = std::distance(subjects.begin(), it);
1116
1117 if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
1118 // exists -> replace
1119 *it = key;
1120 if (!modelResetInProgress()) {
1121 Q_EMIT dataChanged(createIndex(row, 0, const_cast<char *>(issuer_fpr)), createIndex(row, NumColumns - 1, const_cast<char *>(issuer_fpr)));
1122 }
1123 } else {
1124 // doesn't exist -> insert
1125 const std::vector<Key>::const_iterator pos =
1126 Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
1127 Q_ASSERT(pos != mKeysByFingerprint.end());
1128 if (!modelResetInProgress()) {
1129 beginInsertRows(index(*pos), row, row);
1130 }
1131 subjects.insert(it, key);
1132 if (!modelResetInProgress()) {
1133 endInsertRows();
1134 }
1135 }
1136}
1137
1138void HierarchicalKeyListModel::addKeyWithoutParent(const char *issuer_fpr, const Key &key)
1139{
1140 Q_ASSERT(issuer_fpr);
1141 Q_ASSERT(*issuer_fpr);
1142 Q_ASSERT(!key.isNull());
1143
1144 std::vector<Key> &subjects = mKeysByNonExistingParent[issuer_fpr];
1145
1146 // find insertion point:
1147 const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
1148
1149 if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
1150 // exists -> replace
1151 *it = key;
1152 } else {
1153 // doesn't exist -> insert
1154 subjects.insert(it, key);
1155 }
1156
1157 addTopLevelKey(key);
1158}
1159
1160void HierarchicalKeyListModel::addTopLevelKey(const Key &key)
1161{
1162 // find insertion point:
1163 const std::vector<Key>::iterator it = std::lower_bound(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
1164 const int row = std::distance(mTopLevels.begin(), it);
1165
1166 if (it != mTopLevels.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
1167 // exists -> replace
1168 *it = key;
1169 if (!modelResetInProgress()) {
1170 Q_EMIT dataChanged(createIndex(row, 0), createIndex(row, NumColumns - 1));
1171 }
1172 } else {
1173 // doesn't exist -> insert
1174 if (!modelResetInProgress()) {
1175 beginInsertRows(QModelIndex(), row, row);
1176 }
1177 mTopLevels.insert(it, key);
1178 if (!modelResetInProgress()) {
1179 endInsertRows();
1180 }
1181 }
1182}
1183
1184namespace
1185{
1186
1187// based on https://www.boost.org/doc/libs/1_77_0/libs/graph/doc/file_dependency_example.html#sec:cycles
1188struct cycle_detector : public boost::dfs_visitor<> {
1189 cycle_detector(bool &has_cycle)
1190 : _has_cycle{has_cycle}
1191 {
1192 }
1193
1194 template<class Edge, class Graph>
1195 void back_edge(Edge, Graph &)
1196 {
1197 _has_cycle = true;
1198 }
1199
1200private:
1201 bool &_has_cycle;
1202};
1203
1204static bool graph_has_cycle(const boost::adjacency_list<> &graph)
1205{
1206 bool cycle_found = false;
1207 cycle_detector vis{cycle_found};
1208 boost::depth_first_search(graph, visitor(vis));
1209 return cycle_found;
1210}
1211
1212static void find_keys_causing_cycles_and_mask_their_issuers(const std::vector<Key> &keys)
1213{
1214 boost::adjacency_list<> graph{keys.size()};
1215
1216 for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
1217 const auto &key = keys[i];
1218 const char *const issuer_fpr = cleanChainID(key);
1219 if (!issuer_fpr || !*issuer_fpr) {
1220 continue;
1221 }
1222 const std::vector<Key>::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
1223 if (it == keys.end()) {
1224 continue;
1225 }
1226 const auto j = std::distance(keys.begin(), it);
1227 const auto edge = boost::add_edge(i, j, graph).first;
1228 if (graph_has_cycle(graph)) {
1229 Issuers::instance()->maskIssuerOfKey(key);
1230 boost::remove_edge(edge, graph);
1231 }
1232 }
1233}
1234
1235static auto build_key_graph(const std::vector<Key> &keys)
1236{
1237 boost::adjacency_list<> graph(keys.size());
1238
1239 // add edges from children to parents:
1240 for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
1241 const char *const issuer_fpr = cleanChainID(keys[i]);
1242 if (!issuer_fpr || !*issuer_fpr) {
1243 continue;
1244 }
1245 const std::vector<Key>::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
1246 if (it == keys.end()) {
1247 continue;
1248 }
1249 const auto j = std::distance(keys.begin(), it);
1250 add_edge(i, j, graph);
1251 }
1252
1253 return graph;
1254}
1255
1256// sorts 'keys' such that parent always come before their children:
1257static std::vector<Key> topological_sort(const std::vector<Key> &keys)
1258{
1259 const auto graph = build_key_graph(keys);
1260
1261 std::vector<int> order;
1262 order.reserve(keys.size());
1263 topological_sort(graph, std::back_inserter(order));
1264
1265 Q_ASSERT(order.size() == keys.size());
1266
1267 std::vector<Key> result;
1268 result.reserve(keys.size());
1269 for (int i : std::as_const(order)) {
1270 result.push_back(keys[i]);
1271 }
1272 return result;
1273}
1274
1275}
1276
1277QList<QModelIndex> HierarchicalKeyListModel::doAddKeys(const std::vector<Key> &keys)
1278{
1279 Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
1280
1281 if (keys.empty()) {
1282 return QList<QModelIndex>();
1283 }
1284
1285 const std::vector<Key> oldKeys = mKeysByFingerprint;
1286
1287 std::vector<Key> merged;
1288 merged.reserve(keys.size() + mKeysByFingerprint.size());
1289 std::set_union(keys.begin(),
1290 keys.end(),
1291 mKeysByFingerprint.begin(),
1292 mKeysByFingerprint.end(),
1293 std::back_inserter(merged),
1294 _detail::ByFingerprint<std::less>());
1295
1296 mKeysByFingerprint = merged;
1297
1298 if (graph_has_cycle(build_key_graph(mKeysByFingerprint))) {
1299 find_keys_causing_cycles_and_mask_their_issuers(mKeysByFingerprint);
1300 }
1301
1302 std::set<Key, _detail::ByFingerprint<std::less>> changedParents;
1303
1304 const auto topologicalSortedList = topological_sort(keys);
1305 for (const Key &key : topologicalSortedList) {
1306 // check to see whether this key is a parent for a previously parent-less group:
1307 const char *const fpr = key.primaryFingerprint();
1308 if (!fpr || !*fpr) {
1309 continue;
1310 }
1311
1312 const bool keyAlreadyExisted = std::binary_search(oldKeys.begin(), oldKeys.end(), key, _detail::ByFingerprint<std::less>());
1313
1314 const Map::iterator it = mKeysByNonExistingParent.find(fpr);
1315 const std::vector<Key> children = it != mKeysByNonExistingParent.end() ? it->second : std::vector<Key>();
1316 if (it != mKeysByNonExistingParent.end()) {
1317 mKeysByNonExistingParent.erase(it);
1318 }
1319
1320 // Step 1: For new keys, remove children from toplevel:
1321
1322 if (!keyAlreadyExisted) {
1323 auto last = mTopLevels.begin();
1324 auto lastFP = mKeysByFingerprint.begin();
1325
1326 for (const Key &k : children) {
1327 last = Kleo::binary_find(last, mTopLevels.end(), k, _detail::ByFingerprint<std::less>());
1328 Q_ASSERT(last != mTopLevels.end());
1329 const int row = std::distance(mTopLevels.begin(), last);
1330
1331 lastFP = Kleo::binary_find(lastFP, mKeysByFingerprint.end(), k, _detail::ByFingerprint<std::less>());
1332 Q_ASSERT(lastFP != mKeysByFingerprint.end());
1333
1334 Q_EMIT rowAboutToBeMoved(QModelIndex(), row);
1335 if (!modelResetInProgress()) {
1336 beginRemoveRows(QModelIndex(), row, row);
1337 }
1338 last = mTopLevels.erase(last);
1339 lastFP = mKeysByFingerprint.erase(lastFP);
1340 if (!modelResetInProgress()) {
1341 endRemoveRows();
1342 }
1343 }
1344 }
1345 // Step 2: add/update key
1346
1347 const char *const issuer_fpr = cleanChainID(key);
1348 if (!issuer_fpr || !*issuer_fpr) {
1349 // root or something...
1350 addTopLevelKey(key);
1351 } else if (std::binary_search(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>())) {
1352 // parent exists...
1353 addKeyWithParent(issuer_fpr, key);
1354 } else {
1355 // parent doesn't exist yet...
1356 addKeyWithoutParent(issuer_fpr, key);
1357 }
1358
1359 const QModelIndex key_idx = index(key);
1360 QModelIndex key_parent = key_idx.parent();
1361 while (key_parent.isValid()) {
1362 changedParents.insert(doMapToKey(key_parent));
1363 key_parent = key_parent.parent();
1364 }
1365
1366 // Step 3: Add children to new parent ( == key )
1367
1368 if (!keyAlreadyExisted && !children.empty()) {
1369 addKeys(children);
1370 const QModelIndex new_parent = index(key);
1371 // Q_EMIT the rowMoved() signals in reversed direction, so the
1372 // implementation can use a stack for mapping.
1373 for (int i = children.size() - 1; i >= 0; --i) {
1374 Q_EMIT rowMoved(new_parent, i);
1375 }
1376 }
1377 }
1378 // Q_EMIT dataChanged for all parents with new children. This triggers KeyListSortFilterProxyModel to
1379 // show a parent node if it just got children matching the proxy's filter
1380 if (!modelResetInProgress()) {
1381 for (const Key &i : std::as_const(changedParents)) {
1382 const QModelIndex idx = index(i);
1383 if (idx.isValid()) {
1384 Q_EMIT dataChanged(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), NumColumns - 1));
1385 }
1386 }
1387 }
1388 return indexes(keys);
1389}
1390
1391void HierarchicalKeyListModel::doRemoveKey(const Key &key)
1392{
1393 const QModelIndex idx = index(key);
1394 if (!idx.isValid()) {
1395 return;
1396 }
1397
1398 const char *const fpr = key.primaryFingerprint();
1399 if (mKeysByExistingParent.find(fpr) != mKeysByExistingParent.end()) {
1400 // handle non-leave nodes:
1401 std::vector<Key> keys = mKeysByFingerprint;
1402 const std::vector<Key>::iterator it = Kleo::binary_find(keys.begin(), keys.end(), key, _detail::ByFingerprint<std::less>());
1403 if (it == keys.end()) {
1404 return;
1405 }
1406 keys.erase(it);
1407 // FIXME for simplicity, we just clear the model and re-add all keys minus the removed one. This is suboptimal,
1408 // but acceptable given that deletion of non-leave nodes is rather rare.
1409 clear(Keys);
1410 addKeys(keys);
1411 return;
1412 }
1413
1414 // handle leave nodes:
1415
1416 const std::vector<Key>::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
1417
1418 Q_ASSERT(it != mKeysByFingerprint.end());
1419 Q_ASSERT(mKeysByNonExistingParent.find(fpr) == mKeysByNonExistingParent.end());
1420 Q_ASSERT(mKeysByExistingParent.find(fpr) == mKeysByExistingParent.end());
1421
1422 if (!modelResetInProgress()) {
1423 beginRemoveRows(parent(idx), idx.row(), idx.row());
1424 }
1425 mKeysByFingerprint.erase(it);
1426
1427 const char *const issuer_fpr = cleanChainID(key);
1428
1429 const std::vector<Key>::iterator tlIt = Kleo::binary_find(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
1430 if (tlIt != mTopLevels.end()) {
1431 mTopLevels.erase(tlIt);
1432 }
1433
1434 if (issuer_fpr && *issuer_fpr) {
1435 const Map::iterator nexIt = mKeysByNonExistingParent.find(issuer_fpr);
1436 if (nexIt != mKeysByNonExistingParent.end()) {
1437 const std::vector<Key>::iterator eit = Kleo::binary_find(nexIt->second.begin(), nexIt->second.end(), key, _detail::ByFingerprint<std::less>());
1438 if (eit != nexIt->second.end()) {
1439 nexIt->second.erase(eit);
1440 }
1441 if (nexIt->second.empty()) {
1442 mKeysByNonExistingParent.erase(nexIt);
1443 }
1444 }
1445
1446 const Map::iterator exIt = mKeysByExistingParent.find(issuer_fpr);
1447 if (exIt != mKeysByExistingParent.end()) {
1448 const std::vector<Key>::iterator eit = Kleo::binary_find(exIt->second.begin(), exIt->second.end(), key, _detail::ByFingerprint<std::less>());
1449 if (eit != exIt->second.end()) {
1450 exIt->second.erase(eit);
1451 }
1452 if (exIt->second.empty()) {
1453 mKeysByExistingParent.erase(exIt);
1454 }
1455 }
1456 }
1457 if (!modelResetInProgress()) {
1458 endRemoveRows();
1459 }
1460}
1461
1462KeyGroup HierarchicalKeyListModel::doMapToGroup(const QModelIndex &idx) const
1463{
1464 Q_ASSERT(idx.isValid());
1465 if (idx.parent().isValid()) {
1466 // groups are always top-level
1467 return KeyGroup();
1468 }
1469
1470 if (static_cast<unsigned>(idx.row()) >= mTopLevels.size() && static_cast<unsigned>(idx.row()) < mTopLevels.size() + mGroups.size()
1471 && idx.column() < NumColumns) {
1472 return mGroups[idx.row() - mTopLevels.size()];
1473 } else {
1474 return KeyGroup();
1475 }
1476}
1477
1478QModelIndex HierarchicalKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const
1479{
1480 Q_ASSERT(!group.isNull());
1481 const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) {
1482 return g.source() == group.source() && g.id() == group.id();
1483 });
1484 if (it == mGroups.cend()) {
1485 return QModelIndex();
1486 } else {
1487 return createIndex(it - mGroups.cbegin() + mTopLevels.size(), column);
1488 }
1489}
1490
1491void HierarchicalKeyListModel::doSetGroups(const std::vector<KeyGroup> &groups)
1492{
1493 Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared
1494 const int first = mTopLevels.size();
1495 const int last = first + groups.size() - 1;
1496 if (!modelResetInProgress()) {
1497 beginInsertRows(QModelIndex(), first, last);
1498 }
1499 mGroups = groups;
1500 if (!modelResetInProgress()) {
1501 endInsertRows();
1502 }
1503}
1504
1505QModelIndex HierarchicalKeyListModel::doAddGroup(const KeyGroup &group)
1506{
1507 const int newRow = lastGroupRow() + 1;
1508 if (!modelResetInProgress()) {
1509 beginInsertRows(QModelIndex(), newRow, newRow);
1510 }
1511 mGroups.push_back(group);
1512 if (!modelResetInProgress()) {
1513 endInsertRows();
1514 }
1515 return createIndex(newRow, 0);
1516}
1517
1518bool HierarchicalKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group)
1519{
1520 if (group.isNull()) {
1521 return false;
1522 }
1523 const int groupIndex = this->groupIndex(index);
1524 if (groupIndex == -1) {
1525 return false;
1526 }
1527 mGroups[groupIndex] = group;
1528 if (!modelResetInProgress()) {
1529 Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1));
1530 }
1531 return true;
1532}
1533
1534bool HierarchicalKeyListModel::doRemoveGroup(const KeyGroup &group)
1535{
1536 const QModelIndex modelIndex = doMapFromGroup(group, 0);
1537 if (!modelIndex.isValid()) {
1538 return false;
1539 }
1540 const int groupIndex = this->groupIndex(modelIndex);
1541 Q_ASSERT(groupIndex != -1);
1542 if (groupIndex == -1) {
1543 return false;
1544 }
1545 if (!modelResetInProgress()) {
1546 beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row());
1547 }
1548 mGroups.erase(mGroups.begin() + groupIndex);
1549 if (!modelResetInProgress()) {
1550 endRemoveRows();
1551 }
1552 return true;
1553}
1554
1555void HierarchicalKeyListModel::doClear(ItemTypes types)
1556{
1557 if (types & Keys) {
1558 mTopLevels.clear();
1559 mKeysByFingerprint.clear();
1560 mKeysByExistingParent.clear();
1561 mKeysByNonExistingParent.clear();
1562 Issuers::instance()->clear();
1563 }
1564 if (types & Groups) {
1565 mGroups.clear();
1566 }
1567}
1568
1569void AbstractKeyListModel::useKeyCache(bool value, KeyList::Options options)
1570{
1571 d->m_keyListOptions = options;
1572 d->m_useKeyCache = value;
1573 if (!d->m_useKeyCache) {
1574 clear(All);
1575 } else {
1576 d->updateFromKeyCache();
1577 }
1578 connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] {
1579 d->updateFromKeyCache();
1580 });
1581}
1582
1583// static
1584AbstractKeyListModel *AbstractKeyListModel::createFlatKeyListModel(QObject *p)
1585{
1586 AbstractKeyListModel *const m = new FlatKeyListModel(p);
1587#ifdef KLEO_MODEL_TEST
1588 new QAbstractItemModelTester(m, p);
1589#endif
1590 return m;
1591}
1592
1593// static
1594AbstractKeyListModel *AbstractKeyListModel::createHierarchicalKeyListModel(QObject *p)
1595{
1596 AbstractKeyListModel *const m = new HierarchicalKeyListModel(p);
1597#ifdef KLEO_MODEL_TEST
1598 new QAbstractItemModelTester(m, p);
1599#endif
1600 return m;
1601}
1602
1603QMimeData *AbstractKeyListModel::mimeData(const QModelIndexList &indexes) const
1604{
1605 if (d->m_dragHandler) {
1606 return d->m_dragHandler->mimeData(indexes);
1607 } else {
1608 return QAbstractItemModel::mimeData(indexes);
1609 }
1610}
1611
1612Qt::ItemFlags AbstractKeyListModel::flags(const QModelIndex &index) const
1613{
1614 if (d->m_dragHandler) {
1615 return d->m_dragHandler->flags(index);
1616 } else {
1617 return QAbstractItemModel::flags(index);
1618 }
1619}
1620
1621QStringList AbstractKeyListModel::mimeTypes() const
1622{
1623 if (d->m_dragHandler) {
1624 return d->m_dragHandler->mimeTypes();
1625 } else {
1627 }
1628}
1629
1630void AbstractKeyListModel::setDragHandler(const std::shared_ptr<DragHandler> &dragHandler)
1631{
1632 d->m_dragHandler = dragHandler;
1633}
1634
1635#include "keylistmodel.moc"
1636
1637/*!
1638 \fn AbstractKeyListModel::rowAboutToBeMoved( const QModelIndex & old_parent, int old_row )
1639
1640 Emitted before the removal of a row from that model. It will later
1641 be added to the model again, in response to which rowMoved() will be
1642 emitted. If multiple rows are moved in one go, multiple
1643 rowAboutToBeMoved() signals are emitted before the corresponding
1644 number of rowMoved() signals is emitted - in reverse order.
1645
1646 This works around the absence of move semantics in
1647 QAbstractItemModel. Clients can maintain a stack to perform the
1648 QModelIndex-mapping themselves, or, e.g., to preserve the selection
1649 status of the row:
1650
1651 \code
1652 std::vector<bool> mMovingRowWasSelected; // transient, used when rows are moved
1653 // ...
1654 void slotRowAboutToBeMoved( const QModelIndex & p, int row ) {
1655 mMovingRowWasSelected.push_back( selectionModel()->isSelected( model()->index( row, 0, p ) ) );
1656 }
1657 void slotRowMoved( const QModelIndex & p, int row ) {
1658 const bool wasSelected = mMovingRowWasSelected.back();
1659 mMovingRowWasSelected.pop_back();
1660 if ( wasSelected )
1661 selectionModel()->select( model()->index( row, 0, p ), Select|Rows );
1662 }
1663 \endcode
1664
1665 A similar mechanism could be used to preserve the current item during moves.
1666*/
1667
1668/*!
1669 \fn AbstractKeyListModel::rowMoved( const QModelIndex & new_parent, int new_parent )
1670
1671 See rowAboutToBeMoved()
1672*/
1673
1674#include "moc_keylistmodel.cpp"
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)
KGuiItem clear()
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
virtual QMimeData * mimeData(const QModelIndexList &indexes) const const
virtual QStringList mimeTypes() const const
void modelAboutToBeReset()
bool isValid() const const
QIcon fromTheme(const QString &name)
bool isNull() const const
iterator begin()
bool empty() const const
iterator end()
void reserve(qsizetype size)
qsizetype size() const const
int column() const const
void * internalPointer() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QString fromLatin1(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString join(QChar separator) const const
DisplayRole
typedef ItemFlags
Orientation
QTestData & newRow(const char *dataTag)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool canConvert() const const
QVariant fromValue(T &&value)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:09:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.