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

KDE's Doxygen guidelines are available online.