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

KDE's Doxygen guidelines are available online.