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 if (role == ClipboardRole) {
515 return QString::fromLatin1(key.subkey(0).keyGrip());
516 } else {
517 return Formatting::prettyID(key.subkey(0).keyGrip());
518 }
519 case NumColumns:
520 break;
521 }
522 } else if (role == Qt::ToolTipRole) {
523 return Formatting::toolTip(key, toolTipOptions());
524 } else if (role == Qt::FontRole) {
525 return KeyFilterManager::instance()->font(key,
526 (column == ShortKeyID || column == KeyID || column == Fingerprint) ? QFont(QStringLiteral("monospace"))
527 : QFont());
528 } else if (role == Qt::DecorationRole) {
529 return column == Icon ? returnIfValid(KeyFilterManager::instance()->icon(key)) : QVariant();
530 } else if (role == Qt::BackgroundRole) {
531 if (!SystemInfo::isHighContrastModeActive()) {
532 return returnIfValid(KeyFilterManager::instance()->bgColor(key));
533 }
534 } else if (role == Qt::ForegroundRole) {
535 if (!SystemInfo::isHighContrastModeActive()) {
536 return returnIfValid(KeyFilterManager::instance()->fgColor(key));
537 }
538 } else if (role == FingerprintRole) {
539 return QString::fromLatin1(key.primaryFingerprint());
540 } else if (role == KeyRole) {
541 return QVariant::fromValue(key);
542 }
543 return QVariant();
544}
545
546QVariant AbstractKeyListModel::data(const KeyGroup &group, int column, int role) const
547{
548 if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole) {
549 switch (column) {
550 case PrettyName:
551 return group.name();
552 case Validity:
553 return Formatting::complianceStringShort(group);
554 case TechnicalDetails:
555 return Formatting::type(group);
556 case Summary:
557 return Formatting::summaryLine(group); // used for filtering
558 case PrettyEMail:
559 case ValidFrom:
560 case ValidUntil:
561 case ShortKeyID:
562 case KeyID:
563 case Fingerprint:
564 case Issuer:
565 case Origin:
566 case LastUpdate:
567 case SerialNumber:
568 case OwnerTrust:
569 case Remarks:
570 if (role == Qt::AccessibleTextRole) {
571 return i18nc("text for screen readers", "not applicable");
572 }
573 break;
574 case NumColumns:
575 break;
576 }
577 } else if (role == Qt::ToolTipRole) {
578 return Formatting::toolTip(group, toolTipOptions());
579 } else if (role == Qt::FontRole) {
580 return QFont();
581 } else if (role == Qt::DecorationRole) {
582 if (column != Icon && column != Summary) {
583 return QVariant();
584 }
585 return Kleo::all_of(group.keys(),
586 [](const auto &key) {
587 return key.hasEncrypt();
588 })
589 ? QIcon::fromTheme(QStringLiteral("group"))
590 : QIcon::fromTheme(QStringLiteral("emblem-warning"));
591 } else if (role == Qt::BackgroundRole) {
592 } else if (role == Qt::ForegroundRole) {
593 } else if (role == GroupRole) {
594 return QVariant::fromValue(group);
595 }
596 return QVariant();
597}
598
599bool AbstractKeyListModel::setData(const QModelIndex &index, const QVariant &value, int role)
600{
601 Q_UNUSED(role)
602 Q_ASSERT(value.canConvert<KeyGroup>());
603 if (value.canConvert<KeyGroup>()) {
604 const KeyGroup group = value.value<KeyGroup>();
605 return doSetGroupData(index, group);
606 }
607
608 return false;
609}
610
611bool AbstractKeyListModel::modelResetInProgress()
612{
613 return d->m_modelResetInProgress;
614}
615
616namespace
617{
618template<typename Base>
619class TableModelMixin : public Base
620{
621public:
622 explicit TableModelMixin(QObject *p = nullptr)
623 : Base(p)
624 {
625 }
626 ~TableModelMixin() override
627 {
628 }
629
630 using Base::index;
631 QModelIndex index(int row, int column, const QModelIndex &pidx = QModelIndex()) const override
632 {
633 return this->hasIndex(row, column, pidx) ? this->createIndex(row, column, nullptr) : QModelIndex();
634 }
635
636private:
637 QModelIndex parent(const QModelIndex &) const override
638 {
639 return QModelIndex();
640 }
641 bool hasChildren(const QModelIndex &pidx) const override
642 {
643 return (pidx.model() == this || !pidx.isValid()) && this->rowCount(pidx) > 0 && this->columnCount(pidx) > 0;
644 }
645};
646
647class FlatKeyListModel
648#ifndef Q_MOC_RUN
649 : public TableModelMixin<AbstractKeyListModel>
650#else
651 : public AbstractKeyListModel
652#endif
653{
654 Q_OBJECT
655public:
656 explicit FlatKeyListModel(QObject *parent = nullptr);
657 ~FlatKeyListModel() override;
658
659 int rowCount(const QModelIndex &pidx) const override
660 {
661 return pidx.isValid() ? 0 : mKeysByFingerprint.size() + mGroups.size();
662 }
663
664private:
665 Key doMapToKey(const QModelIndex &index) const override;
666 QModelIndex doMapFromKey(const Key &key, int col) const override;
667 QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) override;
668 void doRemoveKey(const Key &key) override;
669
670 KeyGroup doMapToGroup(const QModelIndex &index) const override;
671 QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override;
672 void doSetGroups(const std::vector<KeyGroup> &groups) override;
673 QModelIndex doAddGroup(const KeyGroup &group) override;
674 bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override;
675 bool doRemoveGroup(const KeyGroup &group) override;
676
677 void doClear(ItemTypes types) override
678 {
679 if (types & Keys) {
680 mKeysByFingerprint.clear();
681 }
682 if (types & Groups) {
683 mGroups.clear();
684 }
685 }
686
687 int firstGroupRow() const
688 {
689 return mKeysByFingerprint.size();
690 }
691
692 int lastGroupRow() const
693 {
694 return mKeysByFingerprint.size() + mGroups.size() - 1;
695 }
696
697 int groupIndex(const QModelIndex &index) const
698 {
699 if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) {
700 return -1;
701 }
702 return index.row() - firstGroupRow();
703 }
704
705private:
706 std::vector<Key> mKeysByFingerprint;
707 std::vector<KeyGroup> mGroups;
708};
709
710class HierarchicalKeyListModel : public AbstractKeyListModel
711{
712 Q_OBJECT
713public:
714 explicit HierarchicalKeyListModel(QObject *parent = nullptr);
715 ~HierarchicalKeyListModel() override;
716
717 int rowCount(const QModelIndex &pidx) const override;
718 using AbstractKeyListModel::index;
719 QModelIndex index(int row, int col, const QModelIndex &pidx) const override;
720 QModelIndex parent(const QModelIndex &idx) const override;
721
722 bool hasChildren(const QModelIndex &pidx) const override
723 {
724 return rowCount(pidx) > 0;
725 }
726
727private:
728 Key doMapToKey(const QModelIndex &index) const override;
729 QModelIndex doMapFromKey(const Key &key, int col) const override;
730 QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) override;
731 void doRemoveKey(const Key &key) override;
732
733 KeyGroup doMapToGroup(const QModelIndex &index) const override;
734 QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override;
735 void doSetGroups(const std::vector<KeyGroup> &groups) override;
736 QModelIndex doAddGroup(const KeyGroup &group) override;
737 bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override;
738 bool doRemoveGroup(const KeyGroup &group) override;
739
740 void doClear(ItemTypes types) override;
741
742 int firstGroupRow() const
743 {
744 return mTopLevels.size();
745 }
746
747 int lastGroupRow() const
748 {
749 return mTopLevels.size() + mGroups.size() - 1;
750 }
751
752 int groupIndex(const QModelIndex &index) const
753 {
754 if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) {
755 return -1;
756 }
757 return index.row() - firstGroupRow();
758 }
759
760private:
761 void addTopLevelKey(const Key &key);
762 void addKeyWithParent(const char *issuer_fpr, const Key &key);
763 void addKeyWithoutParent(const char *issuer_fpr, const Key &key);
764
765private:
766 typedef std::map<std::string, std::vector<Key>> Map;
767 std::vector<Key> mKeysByFingerprint; // all keys
768 Map mKeysByExistingParent, mKeysByNonExistingParent; // parent->child map
769 std::vector<Key> mTopLevels; // all roots + parent-less
770 std::vector<KeyGroup> mGroups;
771};
772
773class Issuers
774{
775 Issuers()
776 {
777 }
778
779public:
780 static Issuers *instance()
781 {
782 static auto self = std::unique_ptr<Issuers>{new Issuers{}};
783 return self.get();
784 }
785
786 const char *cleanChainID(const Key &key) const
787 {
788 const char *chainID = "";
789 if (!key.isRoot()) {
790 const char *const chid = key.chainID();
791 if (chid && mKeysWithMaskedIssuer.find(key) == std::end(mKeysWithMaskedIssuer)) {
792 chainID = chid;
793 }
794 }
795 return chainID;
796 }
797
798 void maskIssuerOfKey(const Key &key)
799 {
800 mKeysWithMaskedIssuer.insert(key);
801 }
802
803 void clear()
804 {
805 mKeysWithMaskedIssuer.clear();
806 }
807
808private:
809 std::set<Key, _detail::ByFingerprint<std::less>> mKeysWithMaskedIssuer;
810};
811
812static const char *cleanChainID(const Key &key)
813{
814 return Issuers::instance()->cleanChainID(key);
815}
816
817}
818
819FlatKeyListModel::FlatKeyListModel(QObject *p)
820 : TableModelMixin<AbstractKeyListModel>(p)
821{
822}
823
824FlatKeyListModel::~FlatKeyListModel()
825{
826}
827
828Key FlatKeyListModel::doMapToKey(const QModelIndex &idx) const
829{
830 Q_ASSERT(idx.isValid());
831 if (static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() && idx.column() < NumColumns) {
832 return mKeysByFingerprint[idx.row()];
833 } else {
834 return Key::null;
835 }
836}
837
838QModelIndex FlatKeyListModel::doMapFromKey(const Key &key, int col) const
839{
840 Q_ASSERT(!key.isNull());
841 const std::vector<Key>::const_iterator it =
842 std::lower_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
843 if (it == mKeysByFingerprint.end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
844 return {};
845 } else {
846 return createIndex(it - mKeysByFingerprint.begin(), col);
847 }
848}
849
850QList<QModelIndex> FlatKeyListModel::doAddKeys(const std::vector<Key> &keys)
851{
852 Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
853
854 if (keys.empty()) {
855 return QList<QModelIndex>();
856 }
857
858 for (auto it = keys.begin(), end = keys.end(); it != end; ++it) {
859 // find an insertion point:
860 const std::vector<Key>::iterator pos = std::upper_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), *it, _detail::ByFingerprint<std::less>());
861 const unsigned int idx = std::distance(mKeysByFingerprint.begin(), pos);
862
863 if (idx > 0 && qstrcmp(mKeysByFingerprint[idx - 1].primaryFingerprint(), it->primaryFingerprint()) == 0) {
864 // key existed before - replace with new one:
865 mKeysByFingerprint[idx - 1] = *it;
866 if (!modelResetInProgress()) {
867 Q_EMIT dataChanged(createIndex(idx - 1, 0), createIndex(idx - 1, NumColumns - 1));
868 }
869 } else {
870 // new key - insert:
871 if (!modelResetInProgress()) {
872 beginInsertRows(QModelIndex(), idx, idx);
873 }
874 mKeysByFingerprint.insert(pos, *it);
875 if (!modelResetInProgress()) {
876 endInsertRows();
877 }
878 }
879 }
880
881 return indexes(keys);
882}
883
884void FlatKeyListModel::doRemoveKey(const Key &key)
885{
886 const std::vector<Key>::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
887 if (it == mKeysByFingerprint.end()) {
888 return;
889 }
890
891 const unsigned int row = std::distance(mKeysByFingerprint.begin(), it);
892 if (!modelResetInProgress()) {
893 beginRemoveRows(QModelIndex(), row, row);
894 }
895 mKeysByFingerprint.erase(it);
896 if (!modelResetInProgress()) {
897 endRemoveRows();
898 }
899}
900
901KeyGroup FlatKeyListModel::doMapToGroup(const QModelIndex &idx) const
902{
903 Q_ASSERT(idx.isValid());
904 if (static_cast<unsigned>(idx.row()) >= mKeysByFingerprint.size() && static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() + mGroups.size()
905 && idx.column() < NumColumns) {
906 return mGroups[idx.row() - mKeysByFingerprint.size()];
907 } else {
908 return KeyGroup();
909 }
910}
911
912QModelIndex FlatKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const
913{
914 Q_ASSERT(!group.isNull());
915 const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) {
916 return g.source() == group.source() && g.id() == group.id();
917 });
918 if (it == mGroups.cend()) {
919 return QModelIndex();
920 } else {
921 return createIndex(it - mGroups.cbegin() + mKeysByFingerprint.size(), column);
922 }
923}
924
925void FlatKeyListModel::doSetGroups(const std::vector<KeyGroup> &groups)
926{
927 Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared
928 const int first = mKeysByFingerprint.size();
929 const int last = first + groups.size() - 1;
930 if (!modelResetInProgress()) {
931 beginInsertRows(QModelIndex(), first, last);
932 }
933 mGroups = groups;
934 if (!modelResetInProgress()) {
935 endInsertRows();
936 }
937}
938
939QModelIndex FlatKeyListModel::doAddGroup(const KeyGroup &group)
940{
941 const int newRow = lastGroupRow() + 1;
942 if (!modelResetInProgress()) {
943 beginInsertRows(QModelIndex(), newRow, newRow);
944 }
945 mGroups.push_back(group);
946 if (!modelResetInProgress()) {
947 endInsertRows();
948 }
949 return createIndex(newRow, 0);
950}
951
952bool FlatKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group)
953{
954 if (group.isNull()) {
955 return false;
956 }
957 const int groupIndex = this->groupIndex(index);
958 if (groupIndex == -1) {
959 return false;
960 }
961 mGroups[groupIndex] = group;
962 if (!modelResetInProgress()) {
963 Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1));
964 }
965 return true;
966}
967
968bool FlatKeyListModel::doRemoveGroup(const KeyGroup &group)
969{
970 const QModelIndex modelIndex = doMapFromGroup(group, 0);
971 if (!modelIndex.isValid()) {
972 return false;
973 }
974 const int groupIndex = this->groupIndex(modelIndex);
975 Q_ASSERT(groupIndex != -1);
976 if (groupIndex == -1) {
977 return false;
978 }
979 if (!modelResetInProgress()) {
980 beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row());
981 }
982 mGroups.erase(mGroups.begin() + groupIndex);
983 if (!modelResetInProgress()) {
984 endRemoveRows();
985 }
986 return true;
987}
988
989HierarchicalKeyListModel::HierarchicalKeyListModel(QObject *p)
990 : AbstractKeyListModel(p)
991 , mKeysByFingerprint()
992 , mKeysByExistingParent()
993 , mKeysByNonExistingParent()
994 , mTopLevels()
995{
996}
997
998HierarchicalKeyListModel::~HierarchicalKeyListModel()
999{
1000}
1001
1002int HierarchicalKeyListModel::rowCount(const QModelIndex &pidx) const
1003{
1004 // toplevel item:
1005 if (!pidx.isValid()) {
1006 return mTopLevels.size() + mGroups.size();
1007 }
1008
1009 if (pidx.column() != 0) {
1010 return 0;
1011 }
1012
1013 // non-toplevel item - find the number of subjects for this issuer:
1014 const Key issuer = this->key(pidx);
1015 const char *const fpr = issuer.primaryFingerprint();
1016 if (!fpr || !*fpr) {
1017 return 0;
1018 }
1019 const Map::const_iterator it = mKeysByExistingParent.find(fpr);
1020 if (it == mKeysByExistingParent.end()) {
1021 return 0;
1022 }
1023 return it->second.size();
1024}
1025
1026QModelIndex HierarchicalKeyListModel::index(int row, int col, const QModelIndex &pidx) const
1027{
1028 if (row < 0 || col < 0 || col >= NumColumns) {
1029 return {};
1030 }
1031
1032 // toplevel item:
1033 if (!pidx.isValid()) {
1034 if (static_cast<unsigned>(row) < mTopLevels.size()) {
1035 return index(mTopLevels[row], col);
1036 } else if (static_cast<unsigned>(row) < mTopLevels.size() + mGroups.size()) {
1037 return index(mGroups[row - mTopLevels.size()], col);
1038 } else {
1039 return QModelIndex();
1040 }
1041 }
1042
1043 // non-toplevel item - find the row'th subject of this key:
1044 const Key issuer = this->key(pidx);
1045 const char *const fpr = issuer.primaryFingerprint();
1046 if (!fpr || !*fpr) {
1047 return QModelIndex();
1048 }
1049 const Map::const_iterator it = mKeysByExistingParent.find(fpr);
1050 if (it == mKeysByExistingParent.end() || static_cast<unsigned>(row) >= it->second.size()) {
1051 return QModelIndex();
1052 }
1053 return index(it->second[row], col);
1054}
1055
1056QModelIndex HierarchicalKeyListModel::parent(const QModelIndex &idx) const
1057{
1058 const Key key = this->key(idx);
1059 if (key.isNull() || key.isRoot()) {
1060 return {};
1061 }
1062 const std::vector<Key>::const_iterator it =
1063 Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), cleanChainID(key), _detail::ByFingerprint<std::less>());
1064 return it != mKeysByFingerprint.end() ? index(*it) : QModelIndex();
1065}
1066
1067Key HierarchicalKeyListModel::doMapToKey(const QModelIndex &idx) const
1068{
1069 Key key = Key::null;
1070
1071 if (idx.isValid()) {
1072 const char *const issuer_fpr = static_cast<const char *>(idx.internalPointer());
1073 if (!issuer_fpr || !*issuer_fpr) {
1074 // top-level:
1075 if (static_cast<unsigned>(idx.row()) < mTopLevels.size()) {
1076 key = mTopLevels[idx.row()];
1077 }
1078 } else {
1079 // non-toplevel:
1080 const Map::const_iterator it = mKeysByExistingParent.find(issuer_fpr);
1081 if (it != mKeysByExistingParent.end() && static_cast<unsigned>(idx.row()) < it->second.size()) {
1082 key = it->second[idx.row()];
1083 }
1084 }
1085 }
1086
1087 return key;
1088}
1089
1090QModelIndex HierarchicalKeyListModel::doMapFromKey(const Key &key, int col) const
1091{
1092 if (key.isNull()) {
1093 return {};
1094 }
1095
1096 const char *issuer_fpr = cleanChainID(key);
1097
1098 // we need to look in the toplevels list,...
1099 const std::vector<Key> *v = &mTopLevels;
1100 if (issuer_fpr && *issuer_fpr) {
1101 const std::map<std::string, std::vector<Key>>::const_iterator it = mKeysByExistingParent.find(issuer_fpr);
1102 // ...unless we find an existing parent:
1103 if (it != mKeysByExistingParent.end()) {
1104 v = &it->second;
1105 } else {
1106 issuer_fpr = nullptr; // force internalPointer to zero for toplevels
1107 }
1108 }
1109
1110 const std::vector<Key>::const_iterator it = std::lower_bound(v->begin(), v->end(), key, _detail::ByFingerprint<std::less>());
1111 if (it == v->end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
1112 return QModelIndex();
1113 }
1114
1115 const unsigned int row = std::distance(v->begin(), it);
1116 return createIndex(row, col, const_cast<char * /* thanks, Trolls :/ */>(issuer_fpr));
1117}
1118
1119void HierarchicalKeyListModel::addKeyWithParent(const char *issuer_fpr, const Key &key)
1120{
1121 Q_ASSERT(issuer_fpr);
1122 Q_ASSERT(*issuer_fpr);
1123 Q_ASSERT(!key.isNull());
1124
1125 std::vector<Key> &subjects = mKeysByExistingParent[issuer_fpr];
1126
1127 // find insertion point:
1128 const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
1129 const int row = std::distance(subjects.begin(), it);
1130
1131 if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
1132 // exists -> replace
1133 *it = key;
1134 if (!modelResetInProgress()) {
1135 Q_EMIT dataChanged(createIndex(row, 0, const_cast<char *>(issuer_fpr)), createIndex(row, NumColumns - 1, const_cast<char *>(issuer_fpr)));
1136 }
1137 } else {
1138 // doesn't exist -> insert
1139 const std::vector<Key>::const_iterator pos =
1140 Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
1141 Q_ASSERT(pos != mKeysByFingerprint.end());
1142 if (!modelResetInProgress()) {
1143 beginInsertRows(index(*pos), row, row);
1144 }
1145 subjects.insert(it, key);
1146 if (!modelResetInProgress()) {
1147 endInsertRows();
1148 }
1149 }
1150}
1151
1152void HierarchicalKeyListModel::addKeyWithoutParent(const char *issuer_fpr, const Key &key)
1153{
1154 Q_ASSERT(issuer_fpr);
1155 Q_ASSERT(*issuer_fpr);
1156 Q_ASSERT(!key.isNull());
1157
1158 std::vector<Key> &subjects = mKeysByNonExistingParent[issuer_fpr];
1159
1160 // find insertion point:
1161 const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
1162
1163 if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
1164 // exists -> replace
1165 *it = key;
1166 } else {
1167 // doesn't exist -> insert
1168 subjects.insert(it, key);
1169 }
1170
1171 addTopLevelKey(key);
1172}
1173
1174void HierarchicalKeyListModel::addTopLevelKey(const Key &key)
1175{
1176 // find insertion point:
1177 const std::vector<Key>::iterator it = std::lower_bound(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
1178 const int row = std::distance(mTopLevels.begin(), it);
1179
1180 if (it != mTopLevels.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
1181 // exists -> replace
1182 *it = key;
1183 if (!modelResetInProgress()) {
1184 Q_EMIT dataChanged(createIndex(row, 0), createIndex(row, NumColumns - 1));
1185 }
1186 } else {
1187 // doesn't exist -> insert
1188 if (!modelResetInProgress()) {
1189 beginInsertRows(QModelIndex(), row, row);
1190 }
1191 mTopLevels.insert(it, key);
1192 if (!modelResetInProgress()) {
1193 endInsertRows();
1194 }
1195 }
1196}
1197
1198namespace
1199{
1200
1201// based on https://www.boost.org/doc/libs/1_77_0/libs/graph/doc/file_dependency_example.html#sec:cycles
1202struct cycle_detector : public boost::dfs_visitor<> {
1203 cycle_detector(bool &has_cycle)
1204 : _has_cycle{has_cycle}
1205 {
1206 }
1207
1208 template<class Edge, class Graph>
1209 void back_edge(Edge, Graph &)
1210 {
1211 _has_cycle = true;
1212 }
1213
1214private:
1215 bool &_has_cycle;
1216};
1217
1218static bool graph_has_cycle(const boost::adjacency_list<> &graph)
1219{
1220 bool cycle_found = false;
1221 cycle_detector vis{cycle_found};
1222 boost::depth_first_search(graph, visitor(vis));
1223 return cycle_found;
1224}
1225
1226static void find_keys_causing_cycles_and_mask_their_issuers(const std::vector<Key> &keys)
1227{
1228 boost::adjacency_list<> graph{keys.size()};
1229
1230 for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
1231 const auto &key = keys[i];
1232 const char *const issuer_fpr = cleanChainID(key);
1233 if (!issuer_fpr || !*issuer_fpr) {
1234 continue;
1235 }
1236 const std::vector<Key>::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
1237 if (it == keys.end()) {
1238 continue;
1239 }
1240 const auto j = std::distance(keys.begin(), it);
1241 const auto edge = boost::add_edge(i, j, graph).first;
1242 if (graph_has_cycle(graph)) {
1243 Issuers::instance()->maskIssuerOfKey(key);
1244 boost::remove_edge(edge, graph);
1245 }
1246 }
1247}
1248
1249static auto build_key_graph(const std::vector<Key> &keys)
1250{
1251 boost::adjacency_list<> graph(keys.size());
1252
1253 // add edges from children to parents:
1254 for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
1255 const char *const issuer_fpr = cleanChainID(keys[i]);
1256 if (!issuer_fpr || !*issuer_fpr) {
1257 continue;
1258 }
1259 const std::vector<Key>::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
1260 if (it == keys.end()) {
1261 continue;
1262 }
1263 const auto j = std::distance(keys.begin(), it);
1264 add_edge(i, j, graph);
1265 }
1266
1267 return graph;
1268}
1269
1270// sorts 'keys' such that parent always come before their children:
1271static std::vector<Key> topological_sort(const std::vector<Key> &keys)
1272{
1273 const auto graph = build_key_graph(keys);
1274
1275 std::vector<int> order;
1276 order.reserve(keys.size());
1277 topological_sort(graph, std::back_inserter(order));
1278
1279 Q_ASSERT(order.size() == keys.size());
1280
1281 std::vector<Key> result;
1282 result.reserve(keys.size());
1283 for (int i : std::as_const(order)) {
1284 result.push_back(keys[i]);
1285 }
1286 return result;
1287}
1288
1289}
1290
1291QList<QModelIndex> HierarchicalKeyListModel::doAddKeys(const std::vector<Key> &keys)
1292{
1293 Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
1294
1295 if (keys.empty()) {
1296 return QList<QModelIndex>();
1297 }
1298
1299 const std::vector<Key> oldKeys = mKeysByFingerprint;
1300
1301 std::vector<Key> merged;
1302 merged.reserve(keys.size() + mKeysByFingerprint.size());
1303 std::set_union(keys.begin(),
1304 keys.end(),
1305 mKeysByFingerprint.begin(),
1306 mKeysByFingerprint.end(),
1307 std::back_inserter(merged),
1308 _detail::ByFingerprint<std::less>());
1309
1310 mKeysByFingerprint = merged;
1311
1312 if (graph_has_cycle(build_key_graph(mKeysByFingerprint))) {
1313 find_keys_causing_cycles_and_mask_their_issuers(mKeysByFingerprint);
1314 }
1315
1316 std::set<Key, _detail::ByFingerprint<std::less>> changedParents;
1317
1318 const auto topologicalSortedList = topological_sort(keys);
1319 for (const Key &key : topologicalSortedList) {
1320 // check to see whether this key is a parent for a previously parent-less group:
1321 const char *const fpr = key.primaryFingerprint();
1322 if (!fpr || !*fpr) {
1323 continue;
1324 }
1325
1326 const bool keyAlreadyExisted = std::binary_search(oldKeys.begin(), oldKeys.end(), key, _detail::ByFingerprint<std::less>());
1327
1328 const Map::iterator it = mKeysByNonExistingParent.find(fpr);
1329 const std::vector<Key> children = it != mKeysByNonExistingParent.end() ? it->second : std::vector<Key>();
1330 if (it != mKeysByNonExistingParent.end()) {
1331 mKeysByNonExistingParent.erase(it);
1332 }
1333
1334 // Step 1: For new keys, remove children from toplevel:
1335
1336 if (!keyAlreadyExisted) {
1337 auto last = mTopLevels.begin();
1338 auto lastFP = mKeysByFingerprint.begin();
1339
1340 for (const Key &k : children) {
1341 last = Kleo::binary_find(last, mTopLevels.end(), k, _detail::ByFingerprint<std::less>());
1342 Q_ASSERT(last != mTopLevels.end());
1343 const int row = std::distance(mTopLevels.begin(), last);
1344
1345 lastFP = Kleo::binary_find(lastFP, mKeysByFingerprint.end(), k, _detail::ByFingerprint<std::less>());
1346 Q_ASSERT(lastFP != mKeysByFingerprint.end());
1347
1348 Q_EMIT rowAboutToBeMoved(QModelIndex(), row);
1349 if (!modelResetInProgress()) {
1350 beginRemoveRows(QModelIndex(), row, row);
1351 }
1352 last = mTopLevels.erase(last);
1353 lastFP = mKeysByFingerprint.erase(lastFP);
1354 if (!modelResetInProgress()) {
1355 endRemoveRows();
1356 }
1357 }
1358 }
1359 // Step 2: add/update key
1360
1361 const char *const issuer_fpr = cleanChainID(key);
1362 if (!issuer_fpr || !*issuer_fpr) {
1363 // root or something...
1364 addTopLevelKey(key);
1365 } else if (std::binary_search(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>())) {
1366 // parent exists...
1367 addKeyWithParent(issuer_fpr, key);
1368 } else {
1369 // parent doesn't exist yet...
1370 addKeyWithoutParent(issuer_fpr, key);
1371 }
1372
1373 const QModelIndex key_idx = index(key);
1374 QModelIndex key_parent = key_idx.parent();
1375 while (key_parent.isValid()) {
1376 changedParents.insert(doMapToKey(key_parent));
1377 key_parent = key_parent.parent();
1378 }
1379
1380 // Step 3: Add children to new parent ( == key )
1381
1382 if (!keyAlreadyExisted && !children.empty()) {
1383 addKeys(children);
1384 const QModelIndex new_parent = index(key);
1385 // Q_EMIT the rowMoved() signals in reversed direction, so the
1386 // implementation can use a stack for mapping.
1387 for (int i = children.size() - 1; i >= 0; --i) {
1388 Q_EMIT rowMoved(new_parent, i);
1389 }
1390 }
1391 }
1392 // Q_EMIT dataChanged for all parents with new children. This triggers KeyListSortFilterProxyModel to
1393 // show a parent node if it just got children matching the proxy's filter
1394 if (!modelResetInProgress()) {
1395 for (const Key &i : std::as_const(changedParents)) {
1396 const QModelIndex idx = index(i);
1397 if (idx.isValid()) {
1398 Q_EMIT dataChanged(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), NumColumns - 1));
1399 }
1400 }
1401 }
1402 return indexes(keys);
1403}
1404
1405void HierarchicalKeyListModel::doRemoveKey(const Key &key)
1406{
1407 const QModelIndex idx = index(key);
1408 if (!idx.isValid()) {
1409 return;
1410 }
1411
1412 const char *const fpr = key.primaryFingerprint();
1413 if (mKeysByExistingParent.find(fpr) != mKeysByExistingParent.end()) {
1414 // handle non-leave nodes:
1415 std::vector<Key> keys = mKeysByFingerprint;
1416 const std::vector<Key>::iterator it = Kleo::binary_find(keys.begin(), keys.end(), key, _detail::ByFingerprint<std::less>());
1417 if (it == keys.end()) {
1418 return;
1419 }
1420 keys.erase(it);
1421 // FIXME for simplicity, we just clear the model and re-add all keys minus the removed one. This is suboptimal,
1422 // but acceptable given that deletion of non-leave nodes is rather rare.
1423 clear(Keys);
1424 addKeys(keys);
1425 return;
1426 }
1427
1428 // handle leave nodes:
1429
1430 const std::vector<Key>::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
1431
1432 Q_ASSERT(it != mKeysByFingerprint.end());
1433 Q_ASSERT(mKeysByNonExistingParent.find(fpr) == mKeysByNonExistingParent.end());
1434 Q_ASSERT(mKeysByExistingParent.find(fpr) == mKeysByExistingParent.end());
1435
1436 if (!modelResetInProgress()) {
1437 beginRemoveRows(parent(idx), idx.row(), idx.row());
1438 }
1439 mKeysByFingerprint.erase(it);
1440
1441 const char *const issuer_fpr = cleanChainID(key);
1442
1443 const std::vector<Key>::iterator tlIt = Kleo::binary_find(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
1444 if (tlIt != mTopLevels.end()) {
1445 mTopLevels.erase(tlIt);
1446 }
1447
1448 if (issuer_fpr && *issuer_fpr) {
1449 const Map::iterator nexIt = mKeysByNonExistingParent.find(issuer_fpr);
1450 if (nexIt != mKeysByNonExistingParent.end()) {
1451 const std::vector<Key>::iterator eit = Kleo::binary_find(nexIt->second.begin(), nexIt->second.end(), key, _detail::ByFingerprint<std::less>());
1452 if (eit != nexIt->second.end()) {
1453 nexIt->second.erase(eit);
1454 }
1455 if (nexIt->second.empty()) {
1456 mKeysByNonExistingParent.erase(nexIt);
1457 }
1458 }
1459
1460 const Map::iterator exIt = mKeysByExistingParent.find(issuer_fpr);
1461 if (exIt != mKeysByExistingParent.end()) {
1462 const std::vector<Key>::iterator eit = Kleo::binary_find(exIt->second.begin(), exIt->second.end(), key, _detail::ByFingerprint<std::less>());
1463 if (eit != exIt->second.end()) {
1464 exIt->second.erase(eit);
1465 }
1466 if (exIt->second.empty()) {
1467 mKeysByExistingParent.erase(exIt);
1468 }
1469 }
1470 }
1471 if (!modelResetInProgress()) {
1472 endRemoveRows();
1473 }
1474}
1475
1476KeyGroup HierarchicalKeyListModel::doMapToGroup(const QModelIndex &idx) const
1477{
1478 Q_ASSERT(idx.isValid());
1479 if (idx.parent().isValid()) {
1480 // groups are always top-level
1481 return KeyGroup();
1482 }
1483
1484 if (static_cast<unsigned>(idx.row()) >= mTopLevels.size() && static_cast<unsigned>(idx.row()) < mTopLevels.size() + mGroups.size()
1485 && idx.column() < NumColumns) {
1486 return mGroups[idx.row() - mTopLevels.size()];
1487 } else {
1488 return KeyGroup();
1489 }
1490}
1491
1492QModelIndex HierarchicalKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const
1493{
1494 Q_ASSERT(!group.isNull());
1495 const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) {
1496 return g.source() == group.source() && g.id() == group.id();
1497 });
1498 if (it == mGroups.cend()) {
1499 return QModelIndex();
1500 } else {
1501 return createIndex(it - mGroups.cbegin() + mTopLevels.size(), column);
1502 }
1503}
1504
1505void HierarchicalKeyListModel::doSetGroups(const std::vector<KeyGroup> &groups)
1506{
1507 Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared
1508 const int first = mTopLevels.size();
1509 const int last = first + groups.size() - 1;
1510 if (!modelResetInProgress()) {
1511 beginInsertRows(QModelIndex(), first, last);
1512 }
1513 mGroups = groups;
1514 if (!modelResetInProgress()) {
1515 endInsertRows();
1516 }
1517}
1518
1519QModelIndex HierarchicalKeyListModel::doAddGroup(const KeyGroup &group)
1520{
1521 const int newRow = lastGroupRow() + 1;
1522 if (!modelResetInProgress()) {
1523 beginInsertRows(QModelIndex(), newRow, newRow);
1524 }
1525 mGroups.push_back(group);
1526 if (!modelResetInProgress()) {
1527 endInsertRows();
1528 }
1529 return createIndex(newRow, 0);
1530}
1531
1532bool HierarchicalKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group)
1533{
1534 if (group.isNull()) {
1535 return false;
1536 }
1537 const int groupIndex = this->groupIndex(index);
1538 if (groupIndex == -1) {
1539 return false;
1540 }
1541 mGroups[groupIndex] = group;
1542 if (!modelResetInProgress()) {
1543 Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1));
1544 }
1545 return true;
1546}
1547
1548bool HierarchicalKeyListModel::doRemoveGroup(const KeyGroup &group)
1549{
1550 const QModelIndex modelIndex = doMapFromGroup(group, 0);
1551 if (!modelIndex.isValid()) {
1552 return false;
1553 }
1554 const int groupIndex = this->groupIndex(modelIndex);
1555 Q_ASSERT(groupIndex != -1);
1556 if (groupIndex == -1) {
1557 return false;
1558 }
1559 if (!modelResetInProgress()) {
1560 beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row());
1561 }
1562 mGroups.erase(mGroups.begin() + groupIndex);
1563 if (!modelResetInProgress()) {
1564 endRemoveRows();
1565 }
1566 return true;
1567}
1568
1569void HierarchicalKeyListModel::doClear(ItemTypes types)
1570{
1571 if (types & Keys) {
1572 mTopLevels.clear();
1573 mKeysByFingerprint.clear();
1574 mKeysByExistingParent.clear();
1575 mKeysByNonExistingParent.clear();
1576 Issuers::instance()->clear();
1577 }
1578 if (types & Groups) {
1579 mGroups.clear();
1580 }
1581}
1582
1583void AbstractKeyListModel::useKeyCache(bool value, KeyList::Options options)
1584{
1585 d->m_keyListOptions = options;
1586 d->m_useKeyCache = value;
1587 if (!d->m_useKeyCache) {
1588 clear(All);
1589 } else {
1590 d->updateFromKeyCache();
1591 }
1592 connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] {
1593 d->updateFromKeyCache();
1594 });
1595}
1596
1597// static
1598AbstractKeyListModel *AbstractKeyListModel::createFlatKeyListModel(QObject *p)
1599{
1600 AbstractKeyListModel *const m = new FlatKeyListModel(p);
1601#ifdef KLEO_MODEL_TEST
1602 new QAbstractItemModelTester(m, p);
1603#endif
1604 return m;
1605}
1606
1607// static
1608AbstractKeyListModel *AbstractKeyListModel::createHierarchicalKeyListModel(QObject *p)
1609{
1610 AbstractKeyListModel *const m = new HierarchicalKeyListModel(p);
1611#ifdef KLEO_MODEL_TEST
1612 new QAbstractItemModelTester(m, p);
1613#endif
1614 return m;
1615}
1616
1617QMimeData *AbstractKeyListModel::mimeData(const QModelIndexList &indexes) const
1618{
1619 if (d->m_dragHandler) {
1620 return d->m_dragHandler->mimeData(indexes);
1621 } else {
1622 return QAbstractItemModel::mimeData(indexes);
1623 }
1624}
1625
1626Qt::ItemFlags AbstractKeyListModel::flags(const QModelIndex &index) const
1627{
1628 if (d->m_dragHandler) {
1629 return d->m_dragHandler->flags(index);
1630 } else {
1631 return QAbstractItemModel::flags(index);
1632 }
1633}
1634
1635QStringList AbstractKeyListModel::mimeTypes() const
1636{
1637 if (d->m_dragHandler) {
1638 return d->m_dragHandler->mimeTypes();
1639 } else {
1641 }
1642}
1643
1644void AbstractKeyListModel::setDragHandler(const std::shared_ptr<DragHandler> &dragHandler)
1645{
1646 d->m_dragHandler = dragHandler;
1647}
1648
1649#include "keylistmodel.moc"
1650
1651/*!
1652 \fn AbstractKeyListModel::rowAboutToBeMoved( const QModelIndex & old_parent, int old_row )
1653
1654 Emitted before the removal of a row from that model. It will later
1655 be added to the model again, in response to which rowMoved() will be
1656 emitted. If multiple rows are moved in one go, multiple
1657 rowAboutToBeMoved() signals are emitted before the corresponding
1658 number of rowMoved() signals is emitted - in reverse order.
1659
1660 This works around the absence of move semantics in
1661 QAbstractItemModel. Clients can maintain a stack to perform the
1662 QModelIndex-mapping themselves, or, e.g., to preserve the selection
1663 status of the row:
1664
1665 \code
1666 std::vector<bool> mMovingRowWasSelected; // transient, used when rows are moved
1667 // ...
1668 void slotRowAboutToBeMoved( const QModelIndex & p, int row ) {
1669 mMovingRowWasSelected.push_back( selectionModel()->isSelected( model()->index( row, 0, p ) ) );
1670 }
1671 void slotRowMoved( const QModelIndex & p, int row ) {
1672 const bool wasSelected = mMovingRowWasSelected.back();
1673 mMovingRowWasSelected.pop_back();
1674 if ( wasSelected )
1675 selectionModel()->select( model()->index( row, 0, p ), Select|Rows );
1676 }
1677 \endcode
1678
1679 A similar mechanism could be used to preserve the current item during moves.
1680*/
1681
1682/*!
1683 \fn AbstractKeyListModel::rowMoved( const QModelIndex & new_parent, int new_parent )
1684
1685 See rowAboutToBeMoved()
1686*/
1687
1688#include "moc_keylistmodel.cpp"
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
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)
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 Jul 26 2024 11:50:31 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.