File indexing completed on 2024-06-16 04:56:15

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     view/keytreeview.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "keytreeview.h"
0013 #include "searchbar.h"
0014 
0015 #include <Libkleo/KeyList>
0016 #include <Libkleo/KeyListModel>
0017 #include <Libkleo/KeyListSortFilterProxyModel>
0018 #include <Libkleo/KeyRearrangeColumnsProxyModel>
0019 #include <Libkleo/Predicates>
0020 #include <Libkleo/TreeView>
0021 
0022 #include "utils/headerview.h"
0023 #include "utils/tags.h"
0024 
0025 #include <Libkleo/KeyCache>
0026 #include <Libkleo/KeyFilter>
0027 #include <Libkleo/Stl_Util>
0028 
0029 #include <gpgme++/key.h>
0030 
0031 #include "kleopatra_debug.h"
0032 #include <QAction>
0033 #include <QContextMenuEvent>
0034 #include <QEvent>
0035 #include <QHeaderView>
0036 #include <QItemSelection>
0037 #include <QItemSelectionModel>
0038 #include <QLayout>
0039 #include <QList>
0040 #include <QMenu>
0041 #include <QTimer>
0042 
0043 #include <KLocalizedString>
0044 #include <KSharedConfig>
0045 
0046 static int tagsColumn;
0047 
0048 using namespace Kleo;
0049 using namespace GpgME;
0050 
0051 Q_DECLARE_METATYPE(GpgME::Key)
0052 
0053 namespace
0054 {
0055 
0056 class TreeViewInternal : public Kleo::TreeView
0057 {
0058 public:
0059     explicit TreeViewInternal(QWidget *parent = nullptr)
0060         : Kleo::TreeView{parent}
0061     {
0062         connect(this, &TreeView::columnEnabled, this, [this](int column) {
0063             if (column == tagsColumn) {
0064                 Tags::enableTags();
0065             }
0066             auto tv = qobject_cast<KeyTreeView *>(this->parent());
0067             if (tv) {
0068                 tv->resizeColumns();
0069             }
0070         });
0071         connect(this, &TreeView::columnDisabled, this, [this]() {
0072             auto tv = qobject_cast<KeyTreeView *>(this->parent());
0073             if (tv) {
0074                 tv->resizeColumns();
0075             }
0076         });
0077     }
0078 
0079     QSize minimumSizeHint() const override
0080     {
0081         const QSize min = QTreeView::minimumSizeHint();
0082         return QSize(min.width(), min.height() + 5 * fontMetrics().height());
0083     }
0084 
0085 protected:
0086     void focusInEvent(QFocusEvent *event) override
0087     {
0088         QTreeView::focusInEvent(event);
0089         // queue the invokation, so that it happens after the widget itself got focus
0090         QMetaObject::invokeMethod(this, &TreeViewInternal::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
0091     }
0092 
0093 private:
0094     void forceAccessibleFocusEventForCurrentItem()
0095     {
0096         // force Qt to send a focus event for the current item to accessibility
0097         // tools; otherwise, the user has no idea which item is selected when the
0098         // list gets keyboard input focus
0099         const auto current = currentIndex();
0100         setCurrentIndex({});
0101         setCurrentIndex(current);
0102     }
0103 
0104 private:
0105     QMenu *mHeaderPopup = nullptr;
0106 
0107     QList<QAction *> mColumnActions;
0108 };
0109 
0110 const KeyListModelInterface *keyListModel(const QTreeView &view)
0111 {
0112     const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(view.model());
0113     Q_ASSERT(klmi);
0114     return klmi;
0115 }
0116 
0117 } // anon namespace
0118 
0119 KeyTreeView::KeyTreeView(QWidget *parent)
0120     : QWidget(parent)
0121     , m_proxy(new KeyListSortFilterProxyModel(this))
0122     , m_additionalProxy(nullptr)
0123     , m_view(new TreeViewInternal(this))
0124     , m_flatModel(nullptr)
0125     , m_hierarchicalModel(nullptr)
0126     , m_stringFilter()
0127     , m_keyFilter()
0128     , m_isHierarchical(true)
0129 {
0130     init();
0131 }
0132 
0133 KeyTreeView::KeyTreeView(const KeyTreeView &other)
0134     : QWidget(nullptr)
0135     , m_proxy(new KeyListSortFilterProxyModel(this))
0136     , m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr)
0137     , m_view(new TreeViewInternal(this))
0138     , m_flatModel(other.m_flatModel)
0139     , m_hierarchicalModel(other.m_hierarchicalModel)
0140     , m_stringFilter(other.m_stringFilter)
0141     , m_keyFilter(other.m_keyFilter)
0142     , m_group(other.m_group)
0143     , m_isHierarchical(other.m_isHierarchical)
0144 {
0145     init();
0146     setColumnSizes(other.columnSizes());
0147     setSortColumn(other.sortColumn(), other.sortOrder());
0148 }
0149 
0150 KeyTreeView::KeyTreeView(const QString &text,
0151                          const std::shared_ptr<KeyFilter> &kf,
0152                          AbstractKeyListSortFilterProxyModel *proxy,
0153                          QWidget *parent,
0154                          const KConfigGroup &group)
0155     : QWidget(parent)
0156     , m_proxy(new KeyListSortFilterProxyModel(this))
0157     , m_additionalProxy(proxy)
0158     , m_view(new TreeViewInternal(this))
0159     , m_flatModel(nullptr)
0160     , m_hierarchicalModel(nullptr)
0161     , m_stringFilter(text)
0162     , m_keyFilter(kf)
0163     , m_group(group)
0164     , m_isHierarchical(true)
0165     , m_onceResized(false)
0166 {
0167     init();
0168 }
0169 
0170 void KeyTreeView::setColumnSizes(const std::vector<int> &sizes)
0171 {
0172     if (sizes.empty()) {
0173         return;
0174     }
0175     Q_ASSERT(m_view);
0176     Q_ASSERT(m_view->header());
0177     Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
0178     if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
0179         hv->setSectionSizes(sizes);
0180     }
0181 }
0182 
0183 void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder)
0184 {
0185     Q_ASSERT(m_view);
0186     m_view->sortByColumn(sortColumn, sortOrder);
0187 }
0188 
0189 int KeyTreeView::sortColumn() const
0190 {
0191     Q_ASSERT(m_view);
0192     Q_ASSERT(m_view->header());
0193     return m_view->header()->sortIndicatorSection();
0194 }
0195 
0196 Qt::SortOrder KeyTreeView::sortOrder() const
0197 {
0198     Q_ASSERT(m_view);
0199     Q_ASSERT(m_view->header());
0200     return m_view->header()->sortIndicatorOrder();
0201 }
0202 
0203 std::vector<int> KeyTreeView::columnSizes() const
0204 {
0205     Q_ASSERT(m_view);
0206     Q_ASSERT(m_view->header());
0207     Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
0208     if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
0209         return hv->sectionSizes();
0210     } else {
0211         return std::vector<int>();
0212     }
0213 }
0214 
0215 void KeyTreeView::restoreLayout(const KConfigGroup &group)
0216 {
0217     if (!group.isValid() || !m_view->restoreColumnLayout(group.name())) {
0218         // if config is empty then use default settings
0219         // The numbers have to be in line with the order in
0220         // setsSourceColumns above
0221         m_view->hideColumn(5);
0222 
0223         for (int i = 7; i < m_view->model()->columnCount(); ++i) {
0224             m_view->hideColumn(i);
0225         }
0226         if (KeyCache::instance()->initialized()) {
0227             QTimer::singleShot(0, this, &KeyTreeView::resizeColumns);
0228         }
0229     } else {
0230         m_onceResized = true;
0231     }
0232     if (!m_view->isColumnHidden(tagsColumn)) {
0233         Tags::enableTags();
0234     }
0235 }
0236 
0237 void KeyTreeView::init()
0238 {
0239     KDAB_SET_OBJECT_NAME(m_proxy);
0240     KDAB_SET_OBJECT_NAME(m_view);
0241 
0242     if (m_group.isValid()) {
0243         // Reopen as non const
0244         KConfig *conf = m_group.config();
0245         m_group = conf->group(m_group.name());
0246     }
0247 
0248     if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) {
0249         KDAB_SET_OBJECT_NAME(m_additionalProxy);
0250     }
0251     QLayout *layout = new QVBoxLayout(this);
0252     KDAB_SET_OBJECT_NAME(layout);
0253     layout->setContentsMargins(0, 0, 0, 0);
0254     layout->addWidget(m_view);
0255 
0256     auto headerView = new HeaderView(Qt::Horizontal);
0257     KDAB_SET_OBJECT_NAME(headerView);
0258     headerView->installEventFilter(m_view);
0259     headerView->setSectionsMovable(true);
0260     m_view->setHeader(headerView);
0261 
0262     m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
0263     m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
0264     m_view->setAllColumnsShowFocus(false);
0265     m_view->setSortingEnabled(true);
0266     m_view->setAccessibleName(i18n("Certificates"));
0267     m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
0268     // we show details on double-click
0269     m_view->setExpandsOnDoubleClick(false);
0270 
0271     if (model()) {
0272         if (m_additionalProxy) {
0273             m_additionalProxy->setSourceModel(model());
0274         } else {
0275             m_proxy->setSourceModel(model());
0276         }
0277     }
0278     if (m_additionalProxy) {
0279         m_proxy->setSourceModel(m_additionalProxy);
0280         if (!m_additionalProxy->parent()) {
0281             m_additionalProxy->setParent(this);
0282         }
0283     }
0284 
0285     m_proxy->setFilterRegularExpression(QRegularExpression::escape(m_stringFilter));
0286     m_proxy->setKeyFilter(m_keyFilter);
0287     m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
0288 
0289     auto rearangingModel = new KeyRearrangeColumnsProxyModel(this);
0290     rearangingModel->setSourceModel(m_proxy);
0291     QList<int> columns = {
0292         KeyList::PrettyName,
0293         KeyList::PrettyEMail,
0294         KeyList::Validity,
0295         KeyList::ValidFrom,
0296         KeyList::ValidUntil,
0297         KeyList::TechnicalDetails,
0298         KeyList::KeyID,
0299         KeyList::Fingerprint,
0300         KeyList::OwnerTrust,
0301         KeyList::Origin,
0302         KeyList::LastUpdate,
0303         KeyList::Issuer,
0304         KeyList::SerialNumber,
0305         KeyList::Remarks,
0306         KeyList::Algorithm,
0307         KeyList::Keygrip,
0308     };
0309     tagsColumn = columns.indexOf(KeyList::Remarks);
0310     rearangingModel->setSourceColumns(columns);
0311     m_view->setModel(rearangingModel);
0312 
0313     /* Handle expansion state */
0314     if (m_group.isValid()) {
0315         m_expandedKeys = m_group.readEntry("Expanded", QStringList());
0316     }
0317 
0318     connect(m_view, &QTreeView::expanded, this, [this](const QModelIndex &index) {
0319         if (!index.isValid()) {
0320             return;
0321         }
0322         const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
0323         if (key.isNull()) {
0324             return;
0325         }
0326         const auto fpr = QString::fromLatin1(key.primaryFingerprint());
0327 
0328         if (m_expandedKeys.contains(fpr)) {
0329             return;
0330         }
0331         m_expandedKeys << fpr;
0332         if (m_group.isValid()) {
0333             m_group.writeEntry("Expanded", m_expandedKeys);
0334         }
0335     });
0336 
0337     connect(m_view, &QTreeView::collapsed, this, [this](const QModelIndex &index) {
0338         if (!index.isValid()) {
0339             return;
0340         }
0341         const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
0342         if (key.isNull()) {
0343             return;
0344         }
0345         m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint()));
0346         if (m_group.isValid()) {
0347             m_group.writeEntry("Expanded", m_expandedKeys);
0348         }
0349     });
0350 
0351     updateModelConnections(nullptr, model());
0352 
0353     resizeColumns();
0354 
0355     restoreLayout(m_group);
0356 }
0357 
0358 void KeyTreeView::restoreExpandState()
0359 {
0360     if (!KeyCache::instance()->initialized()) {
0361         qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting.";
0362         return;
0363     }
0364     for (const auto &fpr : std::as_const(m_expandedKeys)) {
0365         const KeyListModelInterface *const km = keyListModel(*m_view);
0366         if (!km) {
0367             qCWarning(KLEOPATRA_LOG) << "invalid model";
0368             return;
0369         }
0370         const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData());
0371         if (key.isNull()) {
0372             qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache";
0373             m_expandedKeys.removeAll(fpr);
0374             return;
0375         }
0376         const auto idx = km->index(key);
0377         if (!idx.isValid()) {
0378             qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model";
0379             m_expandedKeys.removeAll(fpr);
0380             return;
0381         }
0382         m_view->expand(idx);
0383     }
0384 }
0385 
0386 void KeyTreeView::setUpTagKeys()
0387 {
0388     const auto tagKeys = Tags::tagKeys();
0389     if (m_hierarchicalModel) {
0390         m_hierarchicalModel->setRemarkKeys(tagKeys);
0391     }
0392     if (m_flatModel) {
0393         m_flatModel->setRemarkKeys(tagKeys);
0394     }
0395 }
0396 
0397 KeyTreeView::~KeyTreeView() = default;
0398 
0399 static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm)
0400 {
0401     Q_ASSERT(pm);
0402     while (auto const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) {
0403         pm = sm;
0404     }
0405     return pm;
0406 }
0407 
0408 void KeyTreeView::updateModelConnections(AbstractKeyListModel *oldModel, AbstractKeyListModel *newModel)
0409 {
0410     if (oldModel == newModel) {
0411         return;
0412     }
0413     if (oldModel) {
0414         disconnect(oldModel, &QAbstractItemModel::modelAboutToBeReset, this, &KeyTreeView::saveStateBeforeModelChange);
0415         disconnect(oldModel, &QAbstractItemModel::modelReset, this, &KeyTreeView::restoreStateAfterModelChange);
0416         disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &KeyTreeView::saveStateBeforeModelChange);
0417         disconnect(oldModel, &QAbstractItemModel::rowsInserted, this, &KeyTreeView::restoreStateAfterModelChange);
0418         disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &KeyTreeView::saveStateBeforeModelChange);
0419         disconnect(oldModel, &QAbstractItemModel::rowsRemoved, this, &KeyTreeView::restoreStateAfterModelChange);
0420     }
0421     if (newModel) {
0422         connect(newModel, &QAbstractItemModel::modelAboutToBeReset, this, &KeyTreeView::saveStateBeforeModelChange);
0423         connect(newModel, &QAbstractItemModel::modelReset, this, &KeyTreeView::restoreStateAfterModelChange);
0424         connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &KeyTreeView::saveStateBeforeModelChange);
0425         connect(newModel, &QAbstractItemModel::rowsInserted, this, &KeyTreeView::restoreStateAfterModelChange);
0426         connect(newModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &KeyTreeView::saveStateBeforeModelChange);
0427         connect(newModel, &QAbstractItemModel::rowsRemoved, this, &KeyTreeView::restoreStateAfterModelChange);
0428     }
0429 }
0430 
0431 void KeyTreeView::setFlatModel(AbstractKeyListModel *model)
0432 {
0433     if (model == m_flatModel) {
0434         return;
0435     }
0436     auto oldModel = m_flatModel;
0437     m_flatModel = model;
0438     if (!m_isHierarchical)
0439     // TODO: this fails when called after setHierarchicalView( false )...
0440     {
0441         find_last_proxy(m_proxy)->setSourceModel(model);
0442         updateModelConnections(oldModel, model);
0443     }
0444 }
0445 
0446 void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model)
0447 {
0448     if (model == m_hierarchicalModel) {
0449         return;
0450     }
0451     auto oldModel = m_hierarchicalModel;
0452     m_hierarchicalModel = model;
0453     if (m_isHierarchical) {
0454         find_last_proxy(m_proxy)->setSourceModel(model);
0455         updateModelConnections(oldModel, model);
0456         m_view->expandAll();
0457         for (int column = 0; column < m_view->header()->count(); ++column) {
0458             m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column)));
0459         }
0460     }
0461 }
0462 
0463 void KeyTreeView::setStringFilter(const QString &filter)
0464 {
0465     if (filter == m_stringFilter) {
0466         return;
0467     }
0468     m_stringFilter = filter;
0469     m_proxy->setFilterRegularExpression(QRegularExpression::escape(filter));
0470     Q_EMIT stringFilterChanged(filter);
0471 }
0472 
0473 void KeyTreeView::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
0474 {
0475     if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) {
0476         return;
0477     }
0478     m_keyFilter = filter;
0479     m_proxy->setKeyFilter(filter);
0480     Q_EMIT keyFilterChanged(filter);
0481 }
0482 
0483 namespace
0484 {
0485 QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const QTreeView &view)
0486 {
0487     const QModelIndexList indexes = keyListModel(view)->indexes(keys);
0488     return std::accumulate(indexes.cbegin(), indexes.cend(), QItemSelection(), [](QItemSelection selection, const QModelIndex &index) {
0489         if (index.isValid()) {
0490             selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
0491         }
0492         return selection;
0493     });
0494 }
0495 }
0496 
0497 void KeyTreeView::selectKeys(const std::vector<Key> &keys)
0498 {
0499     m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0500 }
0501 
0502 std::vector<Key> KeyTreeView::selectedKeys() const
0503 {
0504     return keyListModel(*m_view)->keys(m_view->selectionModel()->selectedRows());
0505 }
0506 
0507 void KeyTreeView::setHierarchicalView(bool on)
0508 {
0509     if (on == m_isHierarchical) {
0510         return;
0511     }
0512     if (on && !hierarchicalModel()) {
0513         qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set";
0514         return;
0515     }
0516     if (!on && !flatModel()) {
0517         qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set";
0518         return;
0519     }
0520     const std::vector<Key> selectedKeys = this->selectedKeys();
0521     const Key currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
0522 
0523     auto oldModel = model();
0524     m_isHierarchical = on;
0525     find_last_proxy(m_proxy)->setSourceModel(model());
0526     updateModelConnections(oldModel, model());
0527     if (on) {
0528         m_view->expandAll();
0529     }
0530     selectKeys(selectedKeys);
0531     if (!currentKey.isNull()) {
0532         const QModelIndex currentIndex = keyListModel(*m_view)->index(currentKey);
0533         if (currentIndex.isValid()) {
0534             m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
0535             m_view->scrollTo(currentIndex);
0536         }
0537     }
0538     m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
0539     Q_EMIT hierarchicalChanged(on);
0540 }
0541 
0542 void KeyTreeView::setKeys(const std::vector<Key> &keys)
0543 {
0544     std::vector<Key> sorted = keys;
0545     _detail::sort_by_fpr(sorted);
0546     _detail::remove_duplicates_by_fpr(sorted);
0547     m_keys = sorted;
0548     if (m_flatModel) {
0549         m_flatModel->setKeys(sorted);
0550     }
0551     if (m_hierarchicalModel) {
0552         m_hierarchicalModel->setKeys(sorted);
0553     }
0554 }
0555 
0556 void KeyTreeView::addKeysImpl(const std::vector<Key> &keys, bool select)
0557 {
0558     if (keys.empty()) {
0559         return;
0560     }
0561     if (m_keys.empty()) {
0562         setKeys(keys);
0563         return;
0564     }
0565 
0566     std::vector<Key> sorted = keys;
0567     _detail::sort_by_fpr(sorted);
0568     _detail::remove_duplicates_by_fpr(sorted);
0569 
0570     std::vector<Key> newKeys = _detail::union_by_fpr(sorted, m_keys);
0571     m_keys.swap(newKeys);
0572 
0573     if (m_flatModel) {
0574         m_flatModel->addKeys(sorted);
0575     }
0576     if (m_hierarchicalModel) {
0577         m_hierarchicalModel->addKeys(sorted);
0578     }
0579 
0580     if (select) {
0581         selectKeys(sorted);
0582     }
0583 }
0584 
0585 void KeyTreeView::addKeysSelected(const std::vector<Key> &keys)
0586 {
0587     addKeysImpl(keys, true);
0588 }
0589 
0590 void KeyTreeView::addKeysUnselected(const std::vector<Key> &keys)
0591 {
0592     addKeysImpl(keys, false);
0593 }
0594 
0595 void KeyTreeView::removeKeys(const std::vector<Key> &keys)
0596 {
0597     if (keys.empty()) {
0598         return;
0599     }
0600     std::vector<Key> sorted = keys;
0601     _detail::sort_by_fpr(sorted);
0602     _detail::remove_duplicates_by_fpr(sorted);
0603     std::vector<Key> newKeys;
0604     newKeys.reserve(m_keys.size());
0605     std::set_difference(m_keys.begin(), m_keys.end(), sorted.begin(), sorted.end(), std::back_inserter(newKeys), _detail::ByFingerprint<std::less>());
0606     m_keys.swap(newKeys);
0607 
0608     if (m_flatModel) {
0609         std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) {
0610             m_flatModel->removeKey(key);
0611         });
0612     }
0613     if (m_hierarchicalModel) {
0614         std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) {
0615             m_hierarchicalModel->removeKey(key);
0616         });
0617     }
0618 }
0619 
0620 void KeyTreeView::disconnectSearchBar()
0621 {
0622     for (const auto &connection : m_connections) {
0623         disconnect(connection);
0624     }
0625     m_connections.clear();
0626 }
0627 
0628 bool KeyTreeView::connectSearchBar(const SearchBar *bar)
0629 {
0630     m_connections.reserve(4);
0631     m_connections.push_back(connect(this, &KeyTreeView::stringFilterChanged, bar, &SearchBar::setStringFilter));
0632     m_connections.push_back(connect(bar, &SearchBar::stringFilterChanged, this, &KeyTreeView::setStringFilter));
0633     m_connections.push_back(connect(this, &KeyTreeView::keyFilterChanged, bar, &SearchBar::setKeyFilter));
0634     m_connections.push_back(connect(bar, &SearchBar::keyFilterChanged, this, &KeyTreeView::setKeyFilter));
0635 
0636     return std::all_of(m_connections.cbegin(), m_connections.cend(), [](const QMetaObject::Connection &conn) {
0637         return conn;
0638     });
0639 }
0640 
0641 void KeyTreeView::resizeColumns()
0642 {
0643     m_view->setColumnWidth(KeyList::PrettyName, 260);
0644     m_view->setColumnWidth(KeyList::PrettyEMail, 260);
0645 
0646     for (int i = 2; i < m_view->model()->columnCount(); ++i) {
0647         m_view->resizeColumnToContents(i);
0648     }
0649 }
0650 
0651 void KeyTreeView::saveStateBeforeModelChange()
0652 {
0653     m_currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
0654     m_selectedKeys = selectedKeys();
0655 }
0656 
0657 void KeyTreeView::restoreStateAfterModelChange()
0658 {
0659     restoreExpandState();
0660 
0661     selectKeys(m_selectedKeys);
0662     if (!m_currentKey.isNull()) {
0663         const QModelIndex currentIndex = keyListModel(*m_view)->index(m_currentKey);
0664         if (currentIndex.isValid()) {
0665             m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
0666             m_view->scrollTo(currentIndex);
0667         }
0668     }
0669 
0670     setUpTagKeys();
0671     if (!m_onceResized) {
0672         m_onceResized = true;
0673         resizeColumns();
0674     }
0675 }
0676 
0677 #include "moc_keytreeview.cpp"