diff --git a/src/ui/keylistview.cpp b/src/ui/keylistview.cpp index 9f954dda6..96e26db2e 100644 --- a/src/ui/keylistview.cpp +++ b/src/ui/keylistview.cpp @@ -1,560 +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 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; KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f) - : QTreeWidget(parent) + : NavigatableTreeWidget(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); + setAllColumnsShowFocus(false); for (int i = 0; i < numSignalReplacements; ++i) { connect(this, signalReplacements[i].source, signalReplacements[i].target); } this->setToolTip(QString()); viewport()->setToolTip(QString()); // make double sure :) } 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 KeyListView::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { deregisterItem(item); } takeTopLevelItem(indexOfTopLevelItem(qlvi)); } void KeyListView::setHierarchical(bool hier) { if (hier == mHierarchical) { return; } mHierarchical = hier; if (hier) { gatherScattered(); } else { scatterGathered(firstChild()); } } 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 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 KeyListView::clear() { d->updateTimer->stop(); d->keyBuffer.clear(); while (QTreeWidgetItem *item = topLevelItem(0)) { delete item; } QTreeWidget::clear(); } 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 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 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 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 KeyListView::scatterGathered(KeyListViewItem *start) { KeyListViewItem *item = start; while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); 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); } else { takeItem(cur); } addTopLevelItem(cur); } } 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 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 KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col) { if (!item || lvi_cast(item)) { Q_EMIT doubleClicked(static_cast(item), col); } } void KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item) { if (!item || lvi_cast(item)) { Q_EMIT returnPressed(static_cast(item)); } } void KeyListView::slotEmitSelectionChanged() { Q_EMIT selectionChanged(selectedItem()); } 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 // // KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent); setKey(key); } KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent); setKey(key); } KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } 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 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 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 KeyListViewItem::toolTip(int col) const { return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString(); } 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 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 // // KeyListView::ColumnStrategy::~ColumnStrategy() { } 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 KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const { return fm.horizontalAdvance(title(col)) * 2; } QString KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const { return text(key, col); } // // // DisplayStrategy // // KeyListView::DisplayStrategy::~DisplayStrategy() { } // font QFont KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const { return font; } // foreground QColor KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const { return fg; } // background QColor KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const { return bg; } // // // Collection of covariant return reimplementations of QListView(Item) // members: // // KeyListView *KeyListViewItem::listView() const { return static_cast(QTreeWidgetItem::treeWidget()); } KeyListViewItem *KeyListViewItem::nextSibling() const { if (parent()) { const int myIndex = parent()->indexOfChild(const_cast(this)); return static_cast(parent()->child(myIndex + 1)); } const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast(this)); return static_cast(treeWidget()->topLevelItem(myIndex + 1)); } KeyListViewItem *KeyListView::firstChild() const { return static_cast(topLevelItem(0)); } KeyListViewItem *KeyListView::selectedItem() const { QList selection = selectedItems(); if (selection.isEmpty()) { return nullptr; } return selection.first(); } QList KeyListView::selectedItems() const { QList result; const auto selectedItems = QTreeWidget::selectedItems(); for (QTreeWidgetItem *selectedItem : selectedItems) { if (auto *i = lvi_cast(selectedItem)) { result.append(i); } } return result; } bool KeyListView::isMultiSelection() const { return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection; } void KeyListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { if (selectedItem()) { slotEmitReturnPressed(selectedItem()); } } QTreeView::keyPressEvent(event); } diff --git a/src/ui/keylistview.h b/src/ui/keylistview.h index 5728ae528..7ee82c733 100644 --- a/src/ui/keylistview.h +++ b/src/ui/keylistview.h @@ -1,197 +1,198 @@ /* keylistview.h 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 */ #pragma once #include "kleo_export.h" +#include "navigatabletreewidget.h" + #include #include #include -#include #include class QFont; class QColor; namespace Kleo { // work around moc parser bug... #define TEMPLATE_TYPENAME(T) template TEMPLATE_TYPENAME(T) inline T *lvi_cast(QTreeWidgetItem *item) { return item && (item->type() == T::RTTI) ? static_cast(item) : nullptr; } TEMPLATE_TYPENAME(T) inline const T *lvi_cast(const QTreeWidgetItem *item) { return item && (item->type() == T::RTTI) ? static_cast(item) : nullptr; } #undef TEMPLATE_TYPENAME class KeyListView; class KeyListViewItem : public QTreeWidgetItem { public: KeyListViewItem(KeyListView *parent, const GpgME::Key &key); KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key); KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key); KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key); ~KeyListViewItem() override; void setKey(const GpgME::Key &key); const GpgME::Key &key() const { return mKey; } enum { RTTI = QTreeWidgetItem::UserType + 1 }; // // only boring stuff below: // virtual QString toolTip(int column) const; /*! \reimp for covariant return */ KeyListView *listView() const; /*! \reimp for covariant return */ KeyListViewItem *nextSibling() const; /*! \reimp */ bool operator<(const QTreeWidgetItem &other) const override; /*! \reimp */ void takeItem(QTreeWidgetItem *item); private: GpgME::Key mKey; }; -class KLEO_EXPORT KeyListView : public QTreeWidget +class KLEO_EXPORT KeyListView : public NavigatableTreeWidget { Q_OBJECT friend class KeyListViewItem; public: class KLEO_EXPORT ColumnStrategy { public: virtual ~ColumnStrategy(); virtual QString title(int column) const = 0; virtual int width(int column, const QFontMetrics &fm) const; virtual QHeaderView::ResizeMode resizeMode(int) const { return QHeaderView::Interactive; } virtual QString text(const GpgME::Key &key, int column) const = 0; virtual QString toolTip(const GpgME::Key &key, int column) const; virtual QIcon icon(const GpgME::Key &, int) const { return QIcon(); } virtual int compare(const GpgME::Key &key1, const GpgME::Key &key2, const int column) const; }; class KLEO_EXPORT DisplayStrategy { public: virtual ~DisplayStrategy(); // font virtual QFont keyFont(const GpgME::Key &, const QFont &) const; // foreground virtual QColor keyForeground(const GpgME::Key &, const QColor &) const; // background virtual QColor keyBackground(const GpgME::Key &, const QColor &) const; }; explicit KeyListView(const ColumnStrategy *strategy, const DisplayStrategy *display = nullptr, QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~KeyListView() override; const ColumnStrategy *columnStrategy() const { return mColumnStrategy; } const DisplayStrategy *displayStrategy() const { return mDisplayStrategy; } bool hierarchical() const { return mHierarchical; } virtual void setHierarchical(bool hier); void flushKeys() { slotUpdateTimeout(); } bool isMultiSelection() const; KeyListViewItem *itemByFingerprint(const QByteArray &) const; public: using QTreeWidget::selectionChanged; // for below, but moc doesn't like it to be in the Q_SIGNALS: section Q_SIGNALS: void doubleClicked(Kleo::KeyListViewItem *, int); void returnPressed(Kleo::KeyListViewItem *); void selectionChanged(Kleo::KeyListViewItem *); void contextMenu(Kleo::KeyListViewItem *, const QPoint &); protected: void keyPressEvent(QKeyEvent *event) override; public Q_SLOTS: virtual void slotAddKey(const GpgME::Key &key); virtual void slotRefreshKey(const GpgME::Key &key); // // Only boring stuff below: // private Q_SLOTS: void slotEmitDoubleClicked(QTreeWidgetItem *, int); void slotEmitReturnPressed(QTreeWidgetItem *); void slotEmitSelectionChanged(); void slotEmitContextMenu(const QPoint &pos); void slotUpdateTimeout(); public: /*! \reimp for covariant return */ KeyListViewItem *selectedItem() const; /*! \reimp */ QList selectedItems() const; /*! \reimp for covariant return */ KeyListViewItem *firstChild() const; /*! \reimp */ void clear(); /*! \reimp */ void takeItem(QTreeWidgetItem *); private: void doHierarchicalInsert(const GpgME::Key &); void gatherScattered(); void scatterGathered(KeyListViewItem *); void registerItem(KeyListViewItem *); void deregisterItem(const KeyListViewItem *); private: const ColumnStrategy *mColumnStrategy = nullptr; const DisplayStrategy *mDisplayStrategy = nullptr; bool mHierarchical = false; class KeyListViewPrivate; std::unique_ptr const d; }; }