diff --git a/src/ui/keylistview.cpp b/src/ui/keylistview.cpp index 0676f8863..9f954dda6 100644 --- a/src/ui/keylistview.cpp +++ b/src/ui/keylistview.cpp @@ -1,558 +1,560 @@ /* keylistview.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistview.h" #include #include #include #include #include #include #include #include #include #include #include +using namespace Kleo; + static const int updateDelayMilliSecs = 500; -class Q_DECL_HIDDEN Kleo::KeyListView::KeyListViewPrivate +class Q_DECL_HIDDEN KeyListView::KeyListViewPrivate { public: KeyListViewPrivate() : updateTimer(nullptr) { } std::vector keyBuffer; QTimer *updateTimer = nullptr; std::map itemMap; }; // a list of signals where we want to replace QListViewItem with // Kleo:KeyListViewItem: static const struct { const char *source; const char *target; } signalReplacements[] = { { SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), SLOT(slotEmitDoubleClicked(QTreeWidgetItem *, int)), }, { SIGNAL(itemSelectionChanged()), SLOT(slotEmitSelectionChanged()), }, { SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotEmitContextMenu(QPoint)), }, }; static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements; -Kleo::KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f) +KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f) : QTreeWidget(parent) , mColumnStrategy(columnStrategy) , mDisplayStrategy(displayStrategy) , mHierarchical(false) , d(new KeyListViewPrivate()) { setWindowFlags(f); setContextMenuPolicy(Qt::CustomContextMenu); d->updateTimer = new QTimer(this); d->updateTimer->setSingleShot(true); connect(d->updateTimer, &QTimer::timeout, this, &KeyListView::slotUpdateTimeout); if (!columnStrategy) { qCWarning(KLEO_UI_LOG) << "Kleo::KeyListView: need a column strategy to work with!"; return; } const QFontMetrics fm = fontMetrics(); for (int col = 0; !columnStrategy->title(col).isEmpty(); ++col) { headerItem()->setText(col, columnStrategy->title(col)); header()->resizeSection(col, columnStrategy->width(col, fm)); header()->setSectionResizeMode(col, columnStrategy->resizeMode(col)); } setAllColumnsShowFocus(true); for (int i = 0; i < numSignalReplacements; ++i) { connect(this, signalReplacements[i].source, signalReplacements[i].target); } this->setToolTip(QString()); viewport()->setToolTip(QString()); // make double sure :) } -Kleo::KeyListView::~KeyListView() +KeyListView::~KeyListView() { d->updateTimer->stop(); // need to clear here, since in ~QListView, our children won't have // a valid listView() pointing to us anymore, and their dtors try to // unregister from us. clear(); Q_ASSERT(d->itemMap.size() == 0); // need to delete the tooltip ourselves, as ~QToolTip isn't virtual :o delete mColumnStrategy; mColumnStrategy = nullptr; delete mDisplayStrategy; mDisplayStrategy = nullptr; } -void Kleo::KeyListView::takeItem(QTreeWidgetItem *qlvi) +void KeyListView::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { deregisterItem(item); } takeTopLevelItem(indexOfTopLevelItem(qlvi)); } -void Kleo::KeyListView::setHierarchical(bool hier) +void KeyListView::setHierarchical(bool hier) { if (hier == mHierarchical) { return; } mHierarchical = hier; if (hier) { gatherScattered(); } else { scatterGathered(firstChild()); } } -void Kleo::KeyListView::slotAddKey(const GpgME::Key &key) +void KeyListView::slotAddKey(const GpgME::Key &key) { if (key.isNull()) { return; } d->keyBuffer.push_back(key); if (!d->updateTimer->isActive()) { d->updateTimer->start(updateDelayMilliSecs); } } -void Kleo::KeyListView::slotUpdateTimeout() +void KeyListView::slotUpdateTimeout() { if (d->keyBuffer.empty()) { return; } const bool wasUpdatesEnabled = viewport()->updatesEnabled(); if (wasUpdatesEnabled) { viewport()->setUpdatesEnabled(false); } qCDebug(KLEO_UI_LOG) << "Kleo::KeyListView::slotUpdateTimeout(): processing" << d->keyBuffer.size() << "items en block"; if (hierarchical()) { for (std::vector::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { doHierarchicalInsert(*it); } gatherScattered(); } else { for (std::vector::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { (void)new KeyListViewItem(this, *it); } } if (wasUpdatesEnabled) { viewport()->setUpdatesEnabled(true); } d->keyBuffer.clear(); } -void Kleo::KeyListView::clear() +void KeyListView::clear() { d->updateTimer->stop(); d->keyBuffer.clear(); while (QTreeWidgetItem *item = topLevelItem(0)) { delete item; } QTreeWidget::clear(); } -void Kleo::KeyListView::registerItem(KeyListViewItem *item) +void KeyListView::registerItem(KeyListViewItem *item) { // qCDebug(KLEO_UI_LOG) <<"registerItem(" << item <<" )"; if (!item) { return; } const QByteArray fpr = item->key().primaryFingerprint(); if (!fpr.isEmpty()) { d->itemMap.insert(std::make_pair(fpr, item)); } } -void Kleo::KeyListView::deregisterItem(const KeyListViewItem *item) +void KeyListView::deregisterItem(const KeyListViewItem *item) { // qCDebug(KLEO_UI_LOG) <<"deregisterItem( KeyLVI:" << item <<" )"; if (!item) { return; } auto it = d->itemMap.find(item->key().primaryFingerprint()); if (it == d->itemMap.end()) { return; } // This Q_ASSERT triggers, though it shouldn't. Print some more // information when it happens. // Q_ASSERT( it->second == item ); if (it->second != item) { qCWarning(KLEO_UI_LOG) << "deregisterItem:" << "item " << item->key().primaryFingerprint() // << "it->second" << (it->second ? it->second->key().primaryFingerprint() : "is null"); return; } d->itemMap.erase(it); } -void Kleo::KeyListView::doHierarchicalInsert(const GpgME::Key &key) +void KeyListView::doHierarchicalInsert(const GpgME::Key &key) { const QByteArray fpr = key.primaryFingerprint(); if (fpr.isEmpty()) { return; } KeyListViewItem *item = nullptr; if (!key.isRoot()) { if (KeyListViewItem *parent = itemByFingerprint(key.chainID())) { item = new KeyListViewItem(parent, key); parent->setExpanded(true); } } if (!item) { item = new KeyListViewItem(this, key); // top-level (for now) } d->itemMap.insert(std::make_pair(fpr, item)); } -void Kleo::KeyListView::gatherScattered() +void KeyListView::gatherScattered() { KeyListViewItem *item = firstChild(); while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); if (cur->key().isRoot()) { continue; } if (KeyListViewItem *parent = itemByFingerprint(cur->key().chainID())) { // found a new parent... // ### todo: optimize by suppressing removing/adding the item to the itemMap... takeTopLevelItem(indexOfTopLevelItem(cur)); parent->addChild(cur); parent->setExpanded(true); } } } -void Kleo::KeyListView::scatterGathered(KeyListViewItem *start) +void KeyListView::scatterGathered(KeyListViewItem *start) { KeyListViewItem *item = start; while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); - scatterGathered(Kleo::lvi_cast(cur->child(0))); + scatterGathered(lvi_cast(cur->child(0))); Q_ASSERT(cur->childCount() == 0); // ### todo: optimize by suppressing removing/adding the item to the itemMap... if (cur->parent()) { - static_cast(cur->parent())->takeItem(cur); + static_cast(cur->parent())->takeItem(cur); } else { takeItem(cur); } addTopLevelItem(cur); } } -Kleo::KeyListViewItem *Kleo::KeyListView::itemByFingerprint(const QByteArray &s) const +KeyListViewItem *KeyListView::itemByFingerprint(const QByteArray &s) const { if (s.isEmpty()) { return nullptr; } const std::map::const_iterator it = d->itemMap.find(s); if (it == d->itemMap.end()) { return nullptr; } return it->second; } -void Kleo::KeyListView::slotRefreshKey(const GpgME::Key &key) +void KeyListView::slotRefreshKey(const GpgME::Key &key) { const char *fpr = key.primaryFingerprint(); if (!fpr) { return; } if (KeyListViewItem *item = itemByFingerprint(fpr)) { item->setKey(key); } else { // none found -> add it slotAddKey(key); } } // slots for the emission of covariant Q_SIGNALS: -void Kleo::KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col) +void KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col) { if (!item || lvi_cast(item)) { Q_EMIT doubleClicked(static_cast(item), col); } } -void Kleo::KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item) +void KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item) { if (!item || lvi_cast(item)) { Q_EMIT returnPressed(static_cast(item)); } } -void Kleo::KeyListView::slotEmitSelectionChanged() +void KeyListView::slotEmitSelectionChanged() { Q_EMIT selectionChanged(selectedItem()); } -void Kleo::KeyListView::slotEmitContextMenu(const QPoint &pos) +void KeyListView::slotEmitContextMenu(const QPoint &pos) { QTreeWidgetItem *item = itemAt(pos); if (!item || lvi_cast(item)) { Q_EMIT contextMenu(static_cast(item), viewport()->mapToGlobal(pos)); } } // // // KeyListViewItem // // -Kleo::KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key) +KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent); setKey(key); } -Kleo::KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key) +KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent); setKey(key); } -Kleo::KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key) +KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } -Kleo::KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key) +KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } -Kleo::KeyListViewItem::~KeyListViewItem() +KeyListViewItem::~KeyListViewItem() { // delete the children first... When children are deleted in the // QLVI dtor, they don't have listView() anymore, thus they don't // call deregister( this ), leading to stale entries in the // itemMap... while (QTreeWidgetItem *item = child(0)) { delete item; } // better do this here, too, since deletion is top-down and thus // we're deleted when our parent item is no longer a // KeyListViewItem, but a mere QListViewItem, so our takeItem() // overload is gone by that time... if (KeyListView *lv = listView()) { lv->deregisterItem(this); } } -void Kleo::KeyListViewItem::setKey(const GpgME::Key &key) +void KeyListViewItem::setKey(const GpgME::Key &key) { KeyListView *lv = listView(); if (lv) { lv->deregisterItem(this); } mKey = key; if (lv) { lv->registerItem(this); } // the ColumnStrategy operations might be very slow, so cache their // result here, where we're non-const :) - const Kleo::KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr; + const KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr; if (!cs) { return; } const KeyListView::DisplayStrategy *ds = lv->displayStrategy(); const int numCols = lv ? lv->columnCount() : 0; for (int i = 0; i < numCols; ++i) { setText(i, cs->text(key, i)); setToolTip(i, cs->toolTip(key, i)); const QIcon icon = cs->icon(key, i); if (!icon.isNull()) { setIcon(i, icon); } if (ds) { setForeground(i, QBrush(ds->keyForeground(key, foreground(i).color()))); setBackground(i, QBrush(ds->keyBackground(key, background(i).color()))); setFont(i, ds->keyFont(key, font(i))); } } } -QString Kleo::KeyListViewItem::toolTip(int col) const +QString KeyListViewItem::toolTip(int col) const { return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString(); } -bool Kleo::KeyListViewItem::operator<(const QTreeWidgetItem &other) const +bool KeyListViewItem::operator<(const QTreeWidgetItem &other) const { if (other.type() != RTTI || !listView() || !listView()->columnStrategy()) { return QTreeWidgetItem::operator<(other); } const auto that = static_cast(&other); return listView()->columnStrategy()->compare(this->key(), that->key(), treeWidget()->sortColumn()) < 0; } -void Kleo::KeyListViewItem::takeItem(QTreeWidgetItem *qlvi) +void KeyListViewItem::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListViewItem::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { listView()->deregisterItem(item); } takeChild(indexOfChild(qlvi)); } // // // ColumnStrategy // // -Kleo::KeyListView::ColumnStrategy::~ColumnStrategy() +KeyListView::ColumnStrategy::~ColumnStrategy() { } -int Kleo::KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const +int KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const { return QString::localeAwareCompare(text(key1, col), text(key2, col)); } -int Kleo::KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const +int KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const { return fm.horizontalAdvance(title(col)) * 2; } -QString Kleo::KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const +QString KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const { return text(key, col); } // // // DisplayStrategy // // -Kleo::KeyListView::DisplayStrategy::~DisplayStrategy() +KeyListView::DisplayStrategy::~DisplayStrategy() { } // font -QFont Kleo::KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const +QFont KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const { return font; } // foreground -QColor Kleo::KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const +QColor KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const { return fg; } // background -QColor Kleo::KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const +QColor KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const { return bg; } // // // Collection of covariant return reimplementations of QListView(Item) // members: // // -Kleo::KeyListView *Kleo::KeyListViewItem::listView() const +KeyListView *KeyListViewItem::listView() const { - return static_cast(QTreeWidgetItem::treeWidget()); + return static_cast(QTreeWidgetItem::treeWidget()); } -Kleo::KeyListViewItem *Kleo::KeyListViewItem::nextSibling() const +KeyListViewItem *KeyListViewItem::nextSibling() const { if (parent()) { const int myIndex = parent()->indexOfChild(const_cast(this)); - return static_cast(parent()->child(myIndex + 1)); + return static_cast(parent()->child(myIndex + 1)); } const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast(this)); - return static_cast(treeWidget()->topLevelItem(myIndex + 1)); + return static_cast(treeWidget()->topLevelItem(myIndex + 1)); } -Kleo::KeyListViewItem *Kleo::KeyListView::firstChild() const +KeyListViewItem *KeyListView::firstChild() const { - return static_cast(topLevelItem(0)); + return static_cast(topLevelItem(0)); } -Kleo::KeyListViewItem *Kleo::KeyListView::selectedItem() const +KeyListViewItem *KeyListView::selectedItem() const { QList selection = selectedItems(); if (selection.isEmpty()) { return nullptr; } return selection.first(); } -QList Kleo::KeyListView::selectedItems() const +QList KeyListView::selectedItems() const { QList result; const auto selectedItems = QTreeWidget::selectedItems(); for (QTreeWidgetItem *selectedItem : selectedItems) { - if (auto *i = Kleo::lvi_cast(selectedItem)) { + if (auto *i = lvi_cast(selectedItem)) { result.append(i); } } return result; } -bool Kleo::KeyListView::isMultiSelection() const +bool KeyListView::isMultiSelection() const { return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection; } -void Kleo::KeyListView::keyPressEvent(QKeyEvent *event) +void KeyListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { if (selectedItem()) { slotEmitReturnPressed(selectedItem()); } } QTreeView::keyPressEvent(event); }