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

KDE's Doxygen guidelines are available online.