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