1 /* -*- mode: c++; c-basic-offset:4 -*-
2     view/keytreeview.cpp
3 
4     This file is part of Kleopatra, the KDE keymanager
5     SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include <config-kleopatra.h>
11 
12 #include "keytreeview.h"
13 
14 #include <Libkleo/KeyList>
15 #include <Libkleo/KeyListModel>
16 #include <Libkleo/KeyListSortFilterProxyModel>
17 #include <Libkleo/KeyRearrangeColumnsProxyModel>
18 #include <Libkleo/Predicates>
19 
20 #include "utils/headerview.h"
21 #include "utils/tags.h"
22 
23 #include <Libkleo/Stl_Util>
24 #include <Libkleo/KeyFilter>
25 #include <Libkleo/KeyCache>
26 
27 #include <gpgme++/key.h>
28 
29 #include "kleopatra_debug.h"
30 #include <QTimer>
31 #include <QTreeView>
32 #include <QHeaderView>
33 #include <QItemSelectionModel>
34 #include <QItemSelection>
35 #include <QLayout>
36 #include <QList>
37 #include <QMenu>
38 #include <QAction>
39 #include <QEvent>
40 #include <QContextMenuEvent>
41 
42 #include <KSharedConfig>
43 #include <KLocalizedString>
44 
45 #include <gpgme++/gpgmepp_version.h>
46 #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
47 # define GPGME_HAS_REMARKS
48 #endif
49 
50 #define TAGS_COLUMN 13
51 
52 using namespace Kleo;
53 using namespace GpgME;
54 
55 Q_DECLARE_METATYPE(GpgME::Key)
56 
57 namespace
58 {
59 
60 class TreeView : public QTreeView
61 {
62 public:
TreeView(QWidget * parent=nullptr)63     explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent)
64     {
65         header()->installEventFilter(this);
66     }
67 
minimumSizeHint() const68     QSize minimumSizeHint() const override
69     {
70         const QSize min = QTreeView::minimumSizeHint();
71         return QSize(min.width(), min.height() + 5 * fontMetrics().height());
72     }
73 
74 protected:
eventFilter(QObject * watched,QEvent * event)75     bool eventFilter(QObject *watched, QEvent *event) override
76     {
77         Q_UNUSED(watched)
78         if (event->type() == QEvent::ContextMenu) {
79             auto e = static_cast<QContextMenuEvent *>(event);
80 
81             if (!mHeaderPopup) {
82                 mHeaderPopup = new QMenu(this);
83                 mHeaderPopup->setTitle(i18n("View Columns"));
84                 for (int i = 0; i < model()->columnCount(); ++i) {
85                     QAction *tmp
86                         = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString());
87                     tmp->setData(QVariant(i));
88                     tmp->setCheckable(true);
89                     mColumnActions << tmp;
90                 }
91 
92                 connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) {
93                     const int col = action->data().toInt();
94                     if ((col == TAGS_COLUMN) && action->isChecked()) {
95                         Tags::enableTags();
96                     }
97                     if (action->isChecked()) {
98                         showColumn(col);
99                     } else {
100                         hideColumn(col);
101                     }
102 
103                     auto tv = qobject_cast<KeyTreeView *> (parent());
104                     if (tv) {
105                         tv->resizeColumns();
106                     }
107                 });
108             }
109 
110             for (QAction *action : std::as_const(mColumnActions)) {
111                 const int column = action->data().toInt();
112                 action->setChecked(!isColumnHidden(column));
113             }
114 
115             mHeaderPopup->popup(mapToGlobal(e->pos()));
116             return true;
117         }
118 
119         return false;
120     }
121 
122 private:
123     QMenu *mHeaderPopup = nullptr;
124 
125     QList<QAction *> mColumnActions;
126 };
127 
keyListModel(const QTreeView & view)128 const KeyListModelInterface * keyListModel(const QTreeView &view)
129 {
130     const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(view.model());
131     Q_ASSERT(klmi);
132     return klmi;
133 }
134 
135 } // anon namespace
136 
KeyTreeView(QWidget * parent)137 KeyTreeView::KeyTreeView(QWidget *parent)
138     : QWidget(parent),
139       m_proxy(new KeyListSortFilterProxyModel(this)),
140       m_additionalProxy(nullptr),
141       m_view(new TreeView(this)),
142       m_flatModel(nullptr),
143       m_hierarchicalModel(nullptr),
144       m_stringFilter(),
145       m_keyFilter(),
146       m_isHierarchical(true)
147 {
148     init();
149 }
150 
KeyTreeView(const KeyTreeView & other)151 KeyTreeView::KeyTreeView(const KeyTreeView &other)
152     : QWidget(nullptr),
153       m_proxy(new KeyListSortFilterProxyModel(this)),
154       m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr),
155       m_view(new TreeView(this)),
156       m_flatModel(other.m_flatModel),
157       m_hierarchicalModel(other.m_hierarchicalModel),
158       m_stringFilter(other.m_stringFilter),
159       m_keyFilter(other.m_keyFilter),
160       m_group(other.m_group),
161       m_isHierarchical(other.m_isHierarchical)
162 {
163     init();
164     setColumnSizes(other.columnSizes());
165     setSortColumn(other.sortColumn(), other.sortOrder());
166 }
167 
KeyTreeView(const QString & text,const std::shared_ptr<KeyFilter> & kf,AbstractKeyListSortFilterProxyModel * proxy,QWidget * parent,const KConfigGroup & group)168 KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr<KeyFilter> &kf,
169                          AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent,
170                          const KConfigGroup &group)
171     : QWidget(parent),
172       m_proxy(new KeyListSortFilterProxyModel(this)),
173       m_additionalProxy(proxy),
174       m_view(new TreeView(this)),
175       m_flatModel(nullptr),
176       m_hierarchicalModel(nullptr),
177       m_stringFilter(text),
178       m_keyFilter(kf),
179       m_group(group),
180       m_isHierarchical(true),
181       m_onceResized(false)
182 {
183     init();
184 }
185 
setColumnSizes(const std::vector<int> & sizes)186 void KeyTreeView::setColumnSizes(const std::vector<int> &sizes)
187 {
188     if (sizes.empty()) {
189         return;
190     }
191     Q_ASSERT(m_view);
192     Q_ASSERT(m_view->header());
193     Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
194     if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
195         hv->setSectionSizes(sizes);
196     }
197 }
198 
setSortColumn(int sortColumn,Qt::SortOrder sortOrder)199 void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder)
200 {
201     Q_ASSERT(m_view);
202     m_view->sortByColumn(sortColumn, sortOrder);
203 }
204 
sortColumn() const205 int KeyTreeView::sortColumn() const
206 {
207     Q_ASSERT(m_view);
208     Q_ASSERT(m_view->header());
209     return m_view->header()->sortIndicatorSection();
210 }
211 
sortOrder() const212 Qt::SortOrder KeyTreeView::sortOrder() const
213 {
214     Q_ASSERT(m_view);
215     Q_ASSERT(m_view->header());
216     return m_view->header()->sortIndicatorOrder();
217 }
218 
columnSizes() const219 std::vector<int> KeyTreeView::columnSizes() const
220 {
221     Q_ASSERT(m_view);
222     Q_ASSERT(m_view->header());
223     Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
224     if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
225         return hv->sectionSizes();
226     } else {
227         return std::vector<int>();
228     }
229 }
230 
init()231 void KeyTreeView::init()
232 {
233     KDAB_SET_OBJECT_NAME(m_proxy);
234     KDAB_SET_OBJECT_NAME(m_view);
235 
236     if (m_group.isValid()) {
237         // Reopen as non const
238         KConfig *conf = m_group.config();
239         m_group = conf->group(m_group.name());
240     }
241 
242     if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) {
243         KDAB_SET_OBJECT_NAME(m_additionalProxy);
244     }
245     QLayout *layout = new QVBoxLayout(this);
246     KDAB_SET_OBJECT_NAME(layout);
247     layout->setContentsMargins(0, 0, 0, 0);
248     layout->addWidget(m_view);
249 
250     auto headerView = new HeaderView(Qt::Horizontal);
251     KDAB_SET_OBJECT_NAME(headerView);
252     headerView->installEventFilter(m_view);
253     headerView->setSectionsMovable(true);
254     m_view->setHeader(headerView);
255 
256     m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
257     m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
258     //m_view->setAlternatingRowColors( true );
259     m_view->setAllColumnsShowFocus(true);
260     m_view->setSortingEnabled(true);
261 
262     if (model()) {
263         if (m_additionalProxy) {
264             m_additionalProxy->setSourceModel(model());
265         } else {
266             m_proxy->setSourceModel(model());
267         }
268     }
269     if (m_additionalProxy) {
270         m_proxy->setSourceModel(m_additionalProxy);
271         if (!m_additionalProxy->parent()) {
272             m_additionalProxy->setParent(this);
273         }
274     }
275 
276     m_proxy->setFilterFixedString(m_stringFilter);
277     m_proxy->setKeyFilter(m_keyFilter);
278     m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
279 
280     auto rearangingModel = new KeyRearrangeColumnsProxyModel(this);
281     rearangingModel->setSourceModel(m_proxy);
282     rearangingModel->setSourceColumns(QVector<int>() << KeyList::PrettyName
283                                                      << KeyList::PrettyEMail
284                                                      << KeyList::Validity
285                                                      << KeyList::ValidFrom
286                                                      << KeyList::ValidUntil
287                                                      << KeyList::TechnicalDetails
288                                                      << KeyList::KeyID
289                                                      << KeyList::Fingerprint
290                                                      << KeyList::OwnerTrust
291                                                      << KeyList::Origin
292                                                      << KeyList::LastUpdate
293                                                      << KeyList::Issuer
294                                                      << KeyList::SerialNumber
295 #ifdef GPGME_HAS_REMARKS
296     // If a column is added before this TAGS_COLUMN define has to be updated accordingly
297                                                      << KeyList::Remarks
298 #endif
299     );
300     m_view->setModel(rearangingModel);
301 
302     /* Handle expansion state */
303     if (m_group.isValid()) {
304         m_expandedKeys = m_group.readEntry("Expanded", QStringList());
305     }
306 
307     connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) {
308         if (!index.isValid()) {
309             return;
310         }
311         const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
312         if (key.isNull()) {
313             return;
314         }
315         const auto fpr = QString::fromLatin1(key.primaryFingerprint());
316 
317         if (m_expandedKeys.contains(fpr)) {
318             return;
319         }
320         m_expandedKeys << fpr;
321         if (m_group.isValid()) {
322             m_group.writeEntry("Expanded", m_expandedKeys);
323         }
324     });
325 
326     connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) {
327         if (!index.isValid()) {
328             return;
329         }
330         const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
331         if (key.isNull()) {
332             return;
333         }
334         m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint()));
335         if (m_group.isValid()) {
336             m_group.writeEntry("Expanded", m_expandedKeys);
337         }
338     });
339 
340     connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () {
341         /* We use a single shot timer here to ensure that the keysMayHaveChanged
342          * handlers are all handled before we restore the expand state so that
343          * the model is already populated. */
344         QTimer::singleShot(0, [this] () {
345             restoreExpandState();
346             setUpTagKeys();
347             if (!m_onceResized) {
348                 m_onceResized = true;
349                 resizeColumns();
350             }
351         });
352     });
353     resizeColumns();
354     if (m_group.isValid()) {
355         restoreLayout(m_group);
356     }
357 }
358 
restoreExpandState()359 void KeyTreeView::restoreExpandState()
360 {
361     if (!KeyCache::instance()->initialized()) {
362         qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting.";
363         return;
364     }
365     for (const auto &fpr: std::as_const(m_expandedKeys)) {
366         const KeyListModelInterface *const km = keyListModel(*m_view);
367         if (!km) {
368             qCWarning(KLEOPATRA_LOG) << "invalid model";
369             return;
370         }
371         const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData());
372         if (key.isNull()) {
373             qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache";
374             m_expandedKeys.removeAll(fpr);
375             return;
376         }
377         const auto idx = km->index(key);
378         if (!idx.isValid()) {
379             qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model";
380             m_expandedKeys.removeAll(fpr);
381             return;
382         }
383         m_view->expand(idx);
384     }
385 }
386 
setUpTagKeys()387 void KeyTreeView::setUpTagKeys()
388 {
389 #ifdef GPGME_HAS_REMARKS
390     const auto tagKeys = Tags::tagKeys();
391     if (m_hierarchicalModel) {
392         m_hierarchicalModel->setRemarkKeys(tagKeys);
393     }
394     if (m_flatModel) {
395         m_flatModel->setRemarkKeys(tagKeys);
396     }
397 #endif
398 }
399 
saveLayout(KConfigGroup & group)400 void KeyTreeView::saveLayout(KConfigGroup &group)
401 {
402     QHeaderView *header = m_view->header();
403 
404     QVariantList columnVisibility;
405     QVariantList columnOrder;
406     QVariantList columnWidths;
407     const int headerCount = header->count();
408     columnVisibility.reserve(headerCount);
409     columnWidths.reserve(headerCount);
410     columnOrder.reserve(headerCount);
411     for (int i = 0; i < headerCount; ++i) {
412         columnVisibility << QVariant(!m_view->isColumnHidden(i));
413         columnWidths << QVariant(header->sectionSize(i));
414         columnOrder << QVariant(header->visualIndex(i));
415     }
416 
417     group.writeEntry("ColumnVisibility", columnVisibility);
418     group.writeEntry("ColumnOrder", columnOrder);
419     group.writeEntry("ColumnWidths", columnWidths);
420 
421     group.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
422     if (header->isSortIndicatorShown()) {
423         group.writeEntry("SortColumn", header->sortIndicatorSection());
424     } else {
425         group.writeEntry("SortColumn", -1);
426     }
427 }
428 
restoreLayout(const KConfigGroup & group)429 void KeyTreeView::restoreLayout(const KConfigGroup &group)
430 {
431     QHeaderView *header = m_view->header();
432 
433     QVariantList columnVisibility = group.readEntry("ColumnVisibility", QVariantList());
434     QVariantList columnOrder = group.readEntry("ColumnOrder", QVariantList());
435     QVariantList columnWidths = group.readEntry("ColumnWidths", QVariantList());
436 
437     if (columnVisibility.isEmpty()) {
438         // if config is empty then use default settings
439         // The numbers have to be in line with the order in
440         // setsSourceColumns above
441         m_view->hideColumn(5);
442 
443         for (int i = 7; i < m_view->model()->columnCount(); ++i) {
444             m_view->hideColumn(i);
445         }
446         if (KeyCache::instance()->initialized()) {
447             QTimer::singleShot(0, this, &KeyTreeView::resizeColumns);
448         }
449     } else {
450         for (int i = 0; i < header->count(); ++i) {
451             if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) {
452                 // An additional column that was not around last time we saved.
453                 // We default to hidden.
454                 m_view->hideColumn(i);
455                 continue;
456             }
457             bool visible = columnVisibility[i].toBool();
458             int width = columnWidths[i].toInt();
459             int order = columnOrder[i].toInt();
460 
461             header->resizeSection(i, width ? width : 100);
462             header->moveSection(header->visualIndex(i), order);
463             if ((i == TAGS_COLUMN) && visible) {
464                 Tags::enableTags();
465             }
466             if (!visible) {
467                 m_view->hideColumn(i);
468             }
469         }
470         m_onceResized = true;
471     }
472 
473     int sortOrder = group.readEntry("SortAscending", (int)Qt::AscendingOrder);
474     int sortColumn = group.readEntry("SortColumn", 0);
475     if (sortColumn >= 0) {
476         m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
477     }
478 }
479 
~KeyTreeView()480 KeyTreeView::~KeyTreeView()
481 {
482     if (m_group.isValid()) {
483         saveLayout(m_group);
484     }
485 }
486 
find_last_proxy(QAbstractProxyModel * pm)487 static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm)
488 {
489     Q_ASSERT(pm);
490     while (auto const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) {
491         pm = sm;
492     }
493     return pm;
494 }
495 
setFlatModel(AbstractKeyListModel * model)496 void KeyTreeView::setFlatModel(AbstractKeyListModel *model)
497 {
498     if (model == m_flatModel) {
499         return;
500     }
501     m_flatModel = model;
502     if (!m_isHierarchical)
503         // TODO: this fails when called after setHierarchicalView( false )...
504     {
505         find_last_proxy(m_proxy)->setSourceModel(model);
506     }
507 }
508 
setHierarchicalModel(AbstractKeyListModel * model)509 void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model)
510 {
511     if (model == m_hierarchicalModel) {
512         return;
513     }
514     m_hierarchicalModel = model;
515     if (m_isHierarchical) {
516         find_last_proxy(m_proxy)->setSourceModel(model);
517         m_view->expandAll();
518         for (int column = 0; column < m_view->header()->count(); ++column) {
519             m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column)));
520         }
521     }
522 }
523 
setStringFilter(const QString & filter)524 void KeyTreeView::setStringFilter(const QString &filter)
525 {
526     if (filter == m_stringFilter) {
527         return;
528     }
529     m_stringFilter = filter;
530     m_proxy->setFilterFixedString(filter);
531     Q_EMIT stringFilterChanged(filter);
532 }
533 
setKeyFilter(const std::shared_ptr<KeyFilter> & filter)534 void KeyTreeView::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
535 {
536     if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) {
537         return;
538     }
539     m_keyFilter = filter;
540     m_proxy->setKeyFilter(filter);
541     Q_EMIT keyFilterChanged(filter);
542 }
543 
544 namespace
545 {
itemSelectionFromKeys(const std::vector<Key> & keys,const QTreeView & view)546 QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const QTreeView &view)
547 {
548     const QModelIndexList indexes = keyListModel(view)->indexes(keys);
549     return std::accumulate(
550         indexes.cbegin(), indexes.cend(),
551         QItemSelection(),
552         [] (QItemSelection &selection, const QModelIndex &index) {
553             if (index.isValid()) {
554                 selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
555             }
556             return selection;
557         });
558 }
559 }
560 
selectKeys(const std::vector<Key> & keys)561 void KeyTreeView::selectKeys(const std::vector<Key> &keys)
562 {
563     m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view),
564                                      QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
565 }
566 
selectedKeys() const567 std::vector<Key> KeyTreeView::selectedKeys() const
568 {
569     return keyListModel(*m_view)->keys(m_view->selectionModel()->selectedRows());
570 }
571 
setHierarchicalView(bool on)572 void KeyTreeView::setHierarchicalView(bool on)
573 {
574     if (on == m_isHierarchical) {
575         return;
576     }
577     if (on && !hierarchicalModel()) {
578         qCWarning(KLEOPATRA_LOG) <<  "hierarchical view requested, but no hierarchical model set";
579         return;
580     }
581     if (!on && !flatModel()) {
582         qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set";
583         return;
584     }
585     const std::vector<Key> selectedKeys = this->selectedKeys();
586     const Key currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
587 
588     m_isHierarchical = on;
589     find_last_proxy(m_proxy)->setSourceModel(model());
590     if (on) {
591         m_view->expandAll();
592     }
593     selectKeys(selectedKeys);
594     if (!currentKey.isNull()) {
595         const QModelIndex currentIndex = keyListModel(*m_view)->index(currentKey);
596         if (currentIndex.isValid()) {
597             m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
598             m_view->scrollTo(currentIndex);
599         }
600     }
601     Q_EMIT hierarchicalChanged(on);
602 }
603 
setKeys(const std::vector<Key> & keys)604 void KeyTreeView::setKeys(const std::vector<Key> &keys)
605 {
606     std::vector<Key> sorted = keys;
607     _detail::sort_by_fpr(sorted);
608     _detail::remove_duplicates_by_fpr(sorted);
609     m_keys = sorted;
610     if (m_flatModel) {
611         m_flatModel->setKeys(sorted);
612     }
613     if (m_hierarchicalModel) {
614         m_hierarchicalModel->setKeys(sorted);
615     }
616 }
617 
addKeysImpl(const std::vector<Key> & keys,bool select)618 void KeyTreeView::addKeysImpl(const std::vector<Key> &keys, bool select)
619 {
620     if (keys.empty()) {
621         return;
622     }
623     if (m_keys.empty()) {
624         setKeys(keys);
625         return;
626     }
627 
628     std::vector<Key> sorted = keys;
629     _detail::sort_by_fpr(sorted);
630     _detail::remove_duplicates_by_fpr(sorted);
631 
632     std::vector<Key> newKeys = _detail::union_by_fpr(sorted, m_keys);
633     m_keys.swap(newKeys);
634 
635     if (m_flatModel) {
636         m_flatModel->addKeys(sorted);
637     }
638     if (m_hierarchicalModel) {
639         m_hierarchicalModel->addKeys(sorted);
640     }
641 
642     if (select) {
643         selectKeys(sorted);
644     }
645 }
646 
addKeysSelected(const std::vector<Key> & keys)647 void KeyTreeView::addKeysSelected(const std::vector<Key> &keys)
648 {
649     addKeysImpl(keys, true);
650 }
651 
addKeysUnselected(const std::vector<Key> & keys)652 void KeyTreeView::addKeysUnselected(const std::vector<Key> &keys)
653 {
654     addKeysImpl(keys, false);
655 }
656 
removeKeys(const std::vector<Key> & keys)657 void KeyTreeView::removeKeys(const std::vector<Key> &keys)
658 {
659     if (keys.empty()) {
660         return;
661     }
662     std::vector<Key> sorted = keys;
663     _detail::sort_by_fpr(sorted);
664     _detail::remove_duplicates_by_fpr(sorted);
665     std::vector<Key> newKeys;
666     newKeys.reserve(m_keys.size());
667     std::set_difference(m_keys.begin(), m_keys.end(),
668                         sorted.begin(), sorted.end(),
669                         std::back_inserter(newKeys),
670                         _detail::ByFingerprint<std::less>());
671     m_keys.swap(newKeys);
672 
673     if (m_flatModel) {
674         std::for_each(sorted.cbegin(), sorted.cend(),
675                       [this](const Key &key) { m_flatModel->removeKey(key); });
676     }
677     if (m_hierarchicalModel) {
678         std::for_each(sorted.cbegin(), sorted.cend(),
679                       [this](const Key &key) { m_hierarchicalModel->removeKey(key); });
680     }
681 
682 }
683 
684 static const struct {
685     const char *signal;
686     const char *slot;
687 } connections[] = {
688     {
689         SIGNAL(stringFilterChanged(QString)),
690         SLOT(setStringFilter(QString))
691     },
692     {
693         SIGNAL(keyFilterChanged(std::shared_ptr<Kleo::KeyFilter>)),
694         SLOT(setKeyFilter(std::shared_ptr<Kleo::KeyFilter>))
695     },
696 };
697 static const unsigned int numConnections = sizeof connections / sizeof * connections;
698 
disconnectSearchBar(const QObject * bar)699 void KeyTreeView::disconnectSearchBar(const QObject *bar)
700 {
701     for (unsigned int i = 0; i < numConnections; ++i) {
702         disconnect(this, connections[i].signal, bar,  connections[i].slot);
703         disconnect(bar,  connections[i].signal, this, connections[i].slot);
704     }
705 }
706 
connectSearchBar(const QObject * bar)707 bool KeyTreeView::connectSearchBar(const QObject *bar)
708 {
709     for (unsigned int i = 0; i < numConnections; ++i)
710         if (!connect(this, connections[i].signal, bar,  connections[i].slot) ||
711                 !connect(bar,  connections[i].signal, this, connections[i].slot)) {
712             return false;
713         }
714     return true;
715 }
716 
resizeColumns()717 void KeyTreeView::resizeColumns()
718 {
719     m_view->setColumnWidth(KeyList::PrettyName, 260);
720     m_view->setColumnWidth(KeyList::PrettyEMail, 260);
721 
722     for (int i = 2; i < m_view->model()->columnCount(); ++i) {
723         m_view->resizeColumnToContents(i);
724     }
725 }
726