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

KDE's Doxygen guidelines are available online.