File indexing completed on 2025-01-05 04:55:48
0001 /* 0002 keylistview.cpp 0003 0004 This file is part of libkleopatra, the KDE keymanagement library 0005 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include <config-libkleo.h> 0011 0012 #include "keylistview.h" 0013 0014 #include <kleo_ui_debug.h> 0015 0016 #include <QColor> 0017 #include <QFont> 0018 #include <QFontMetrics> 0019 #include <QKeyEvent> 0020 #include <QPoint> 0021 #include <QTimer> 0022 #include <QToolTip> 0023 0024 #include <gpgme++/key.h> 0025 0026 #include <map> 0027 #include <vector> 0028 0029 using namespace Kleo; 0030 0031 static const int updateDelayMilliSecs = 500; 0032 0033 class Q_DECL_HIDDEN KeyListView::KeyListViewPrivate 0034 { 0035 public: 0036 KeyListViewPrivate() 0037 : updateTimer(nullptr) 0038 { 0039 } 0040 0041 std::vector<GpgME::Key> keyBuffer; 0042 QTimer *updateTimer = nullptr; 0043 std::map<QByteArray, KeyListViewItem *> itemMap; 0044 }; 0045 0046 // a list of signals where we want to replace QListViewItem with 0047 // Kleo:KeyListViewItem: 0048 static const struct { 0049 const char *source; 0050 const char *target; 0051 } signalReplacements[] = { 0052 { 0053 SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), 0054 SLOT(slotEmitDoubleClicked(QTreeWidgetItem *, int)), 0055 }, 0056 { 0057 SIGNAL(itemSelectionChanged()), 0058 SLOT(slotEmitSelectionChanged()), 0059 }, 0060 { 0061 SIGNAL(customContextMenuRequested(QPoint)), 0062 SLOT(slotEmitContextMenu(QPoint)), 0063 }, 0064 }; 0065 static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements; 0066 0067 KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f) 0068 : TreeWidget(parent) 0069 , mColumnStrategy(columnStrategy) 0070 , mDisplayStrategy(displayStrategy) 0071 , mHierarchical(false) 0072 , d(new KeyListViewPrivate()) 0073 { 0074 setWindowFlags(f); 0075 setContextMenuPolicy(Qt::CustomContextMenu); 0076 0077 d->updateTimer = new QTimer(this); 0078 d->updateTimer->setSingleShot(true); 0079 connect(d->updateTimer, &QTimer::timeout, this, &KeyListView::slotUpdateTimeout); 0080 if (!columnStrategy) { 0081 qCWarning(KLEO_UI_LOG) << "Kleo::KeyListView: need a column strategy to work with!"; 0082 return; 0083 } 0084 0085 const QFontMetrics fm = fontMetrics(); 0086 0087 for (int col = 0; !columnStrategy->title(col).isEmpty(); ++col) { 0088 headerItem()->setText(col, columnStrategy->title(col)); 0089 header()->resizeSection(col, columnStrategy->width(col, fm)); 0090 header()->setSectionResizeMode(col, columnStrategy->resizeMode(col)); 0091 } 0092 0093 setAllColumnsShowFocus(false); 0094 0095 for (int i = 0; i < numSignalReplacements; ++i) { 0096 connect(this, signalReplacements[i].source, signalReplacements[i].target); 0097 } 0098 0099 this->setToolTip(QString()); 0100 viewport()->setToolTip(QString()); // make double sure :) 0101 } 0102 0103 KeyListView::~KeyListView() 0104 { 0105 d->updateTimer->stop(); 0106 // need to clear here, since in ~QListView, our children won't have 0107 // a valid listView() pointing to us anymore, and their dtors try to 0108 // unregister from us. 0109 clear(); 0110 Q_ASSERT(d->itemMap.size() == 0); 0111 // need to delete the tooltip ourselves, as ~QToolTip isn't virtual :o 0112 delete mColumnStrategy; 0113 mColumnStrategy = nullptr; 0114 delete mDisplayStrategy; 0115 mDisplayStrategy = nullptr; 0116 } 0117 0118 void KeyListView::takeItem(QTreeWidgetItem *qlvi) 0119 { 0120 // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )"; 0121 if (auto *item = lvi_cast<KeyListViewItem>(qlvi)) { 0122 deregisterItem(item); 0123 } 0124 takeTopLevelItem(indexOfTopLevelItem(qlvi)); 0125 } 0126 0127 void KeyListView::setHierarchical(bool hier) 0128 { 0129 if (hier == mHierarchical) { 0130 return; 0131 } 0132 mHierarchical = hier; 0133 if (hier) { 0134 gatherScattered(); 0135 } else { 0136 scatterGathered(firstChild()); 0137 } 0138 } 0139 0140 void KeyListView::slotAddKey(const GpgME::Key &key) 0141 { 0142 if (key.isNull()) { 0143 return; 0144 } 0145 0146 d->keyBuffer.push_back(key); 0147 if (!d->updateTimer->isActive()) { 0148 d->updateTimer->start(updateDelayMilliSecs); 0149 } 0150 } 0151 0152 void KeyListView::slotUpdateTimeout() 0153 { 0154 if (d->keyBuffer.empty()) { 0155 return; 0156 } 0157 0158 const bool wasUpdatesEnabled = viewport()->updatesEnabled(); 0159 if (wasUpdatesEnabled) { 0160 viewport()->setUpdatesEnabled(false); 0161 } 0162 qCDebug(KLEO_UI_LOG) << "Kleo::KeyListView::slotUpdateTimeout(): processing" << d->keyBuffer.size() << "items en block"; 0163 if (hierarchical()) { 0164 for (std::vector<GpgME::Key>::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { 0165 doHierarchicalInsert(*it); 0166 } 0167 gatherScattered(); 0168 } else { 0169 for (std::vector<GpgME::Key>::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { 0170 (void)new KeyListViewItem(this, *it); 0171 } 0172 } 0173 if (wasUpdatesEnabled) { 0174 viewport()->setUpdatesEnabled(true); 0175 } 0176 d->keyBuffer.clear(); 0177 } 0178 0179 void KeyListView::clear() 0180 { 0181 d->updateTimer->stop(); 0182 d->keyBuffer.clear(); 0183 while (QTreeWidgetItem *item = topLevelItem(0)) { 0184 delete item; 0185 } 0186 QTreeWidget::clear(); 0187 } 0188 0189 void KeyListView::registerItem(KeyListViewItem *item) 0190 { 0191 // qCDebug(KLEO_UI_LOG) <<"registerItem(" << item <<" )"; 0192 if (!item) { 0193 return; 0194 } 0195 const QByteArray fpr = item->key().primaryFingerprint(); 0196 if (!fpr.isEmpty()) { 0197 d->itemMap.insert(std::make_pair(fpr, item)); 0198 } 0199 } 0200 0201 void KeyListView::deregisterItem(const KeyListViewItem *item) 0202 { 0203 // qCDebug(KLEO_UI_LOG) <<"deregisterItem( KeyLVI:" << item <<" )"; 0204 if (!item) { 0205 return; 0206 } 0207 auto it = d->itemMap.find(item->key().primaryFingerprint()); 0208 if (it == d->itemMap.end()) { 0209 return; 0210 } 0211 // This Q_ASSERT triggers, though it shouldn't. Print some more 0212 // information when it happens. 0213 // Q_ASSERT( it->second == item ); 0214 if (it->second != item) { 0215 qCWarning(KLEO_UI_LOG) << "deregisterItem:" 0216 << "item " << item->key().primaryFingerprint() // 0217 << "it->second" << (it->second ? it->second->key().primaryFingerprint() : "is null"); 0218 return; 0219 } 0220 d->itemMap.erase(it); 0221 } 0222 0223 void KeyListView::doHierarchicalInsert(const GpgME::Key &key) 0224 { 0225 const QByteArray fpr = key.primaryFingerprint(); 0226 if (fpr.isEmpty()) { 0227 return; 0228 } 0229 KeyListViewItem *item = nullptr; 0230 if (!key.isRoot()) { 0231 if (KeyListViewItem *parent = itemByFingerprint(key.chainID())) { 0232 item = new KeyListViewItem(parent, key); 0233 parent->setExpanded(true); 0234 } 0235 } 0236 if (!item) { 0237 item = new KeyListViewItem(this, key); // top-level (for now) 0238 } 0239 0240 d->itemMap.insert(std::make_pair(fpr, item)); 0241 } 0242 0243 void KeyListView::gatherScattered() 0244 { 0245 KeyListViewItem *item = firstChild(); 0246 while (item) { 0247 KeyListViewItem *cur = item; 0248 item = item->nextSibling(); 0249 if (cur->key().isRoot()) { 0250 continue; 0251 } 0252 if (KeyListViewItem *parent = itemByFingerprint(cur->key().chainID())) { 0253 // found a new parent... 0254 // ### todo: optimize by suppressing removing/adding the item to the itemMap... 0255 takeTopLevelItem(indexOfTopLevelItem(cur)); 0256 parent->addChild(cur); 0257 parent->setExpanded(true); 0258 } 0259 } 0260 } 0261 0262 void KeyListView::scatterGathered(KeyListViewItem *start) 0263 { 0264 KeyListViewItem *item = start; 0265 while (item) { 0266 KeyListViewItem *cur = item; 0267 item = item->nextSibling(); 0268 0269 scatterGathered(lvi_cast<KeyListViewItem>(cur->child(0))); 0270 Q_ASSERT(cur->childCount() == 0); 0271 0272 // ### todo: optimize by suppressing removing/adding the item to the itemMap... 0273 if (cur->parent()) { 0274 static_cast<KeyListViewItem *>(cur->parent())->takeItem(cur); 0275 } else { 0276 takeItem(cur); 0277 } 0278 addTopLevelItem(cur); 0279 } 0280 } 0281 0282 KeyListViewItem *KeyListView::itemByFingerprint(const QByteArray &s) const 0283 { 0284 if (s.isEmpty()) { 0285 return nullptr; 0286 } 0287 const std::map<QByteArray, KeyListViewItem *>::const_iterator it = d->itemMap.find(s); 0288 if (it == d->itemMap.end()) { 0289 return nullptr; 0290 } 0291 return it->second; 0292 } 0293 0294 void KeyListView::slotRefreshKey(const GpgME::Key &key) 0295 { 0296 const char *fpr = key.primaryFingerprint(); 0297 if (!fpr) { 0298 return; 0299 } 0300 if (KeyListViewItem *item = itemByFingerprint(fpr)) { 0301 item->setKey(key); 0302 } else { 0303 // none found -> add it 0304 slotAddKey(key); 0305 } 0306 } 0307 0308 // slots for the emission of covariant Q_SIGNALS: 0309 0310 void KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col) 0311 { 0312 if (!item || lvi_cast<KeyListViewItem>(item)) { 0313 Q_EMIT doubleClicked(static_cast<KeyListViewItem *>(item), col); 0314 } 0315 } 0316 0317 void KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item) 0318 { 0319 if (!item || lvi_cast<KeyListViewItem>(item)) { 0320 Q_EMIT returnPressed(static_cast<KeyListViewItem *>(item)); 0321 } 0322 } 0323 0324 void KeyListView::slotEmitSelectionChanged() 0325 { 0326 Q_EMIT selectionChanged(selectedItem()); 0327 } 0328 0329 void KeyListView::slotEmitContextMenu(const QPoint &pos) 0330 { 0331 QTreeWidgetItem *item = itemAt(pos); 0332 if (!item || lvi_cast<KeyListViewItem>(item)) { 0333 Q_EMIT contextMenu(static_cast<KeyListViewItem *>(item), viewport()->mapToGlobal(pos)); 0334 } 0335 } 0336 0337 // 0338 // 0339 // KeyListViewItem 0340 // 0341 // 0342 0343 KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key) 0344 : QTreeWidgetItem(parent, RTTI) 0345 { 0346 Q_ASSERT(parent); 0347 setKey(key); 0348 } 0349 0350 KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key) 0351 : QTreeWidgetItem(parent, after, RTTI) 0352 { 0353 Q_ASSERT(parent); 0354 setKey(key); 0355 } 0356 0357 KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key) 0358 : QTreeWidgetItem(parent, RTTI) 0359 { 0360 Q_ASSERT(parent && parent->listView()); 0361 setKey(key); 0362 } 0363 0364 KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key) 0365 : QTreeWidgetItem(parent, after, RTTI) 0366 { 0367 Q_ASSERT(parent && parent->listView()); 0368 setKey(key); 0369 } 0370 0371 KeyListViewItem::~KeyListViewItem() 0372 { 0373 // delete the children first... When children are deleted in the 0374 // QLVI dtor, they don't have listView() anymore, thus they don't 0375 // call deregister( this ), leading to stale entries in the 0376 // itemMap... 0377 while (QTreeWidgetItem *item = child(0)) { 0378 delete item; 0379 } 0380 // better do this here, too, since deletion is top-down and thus 0381 // we're deleted when our parent item is no longer a 0382 // KeyListViewItem, but a mere QListViewItem, so our takeItem() 0383 // overload is gone by that time... 0384 if (KeyListView *lv = listView()) { 0385 lv->deregisterItem(this); 0386 } 0387 } 0388 0389 void KeyListViewItem::setKey(const GpgME::Key &key) 0390 { 0391 KeyListView *lv = listView(); 0392 if (lv) { 0393 lv->deregisterItem(this); 0394 } 0395 mKey = key; 0396 if (lv) { 0397 lv->registerItem(this); 0398 } 0399 0400 // the ColumnStrategy operations might be very slow, so cache their 0401 // result here, where we're non-const :) 0402 const KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr; 0403 if (!cs) { 0404 return; 0405 } 0406 const KeyListView::DisplayStrategy *ds = lv->displayStrategy(); 0407 const int numCols = lv ? lv->columnCount() : 0; 0408 for (int i = 0; i < numCols; ++i) { 0409 setText(i, cs->text(key, i)); 0410 const auto accessibleText = cs->accessibleText(key, i); 0411 if (!accessibleText.isEmpty()) { 0412 setData(i, Qt::AccessibleTextRole, accessibleText); 0413 } 0414 setToolTip(i, cs->toolTip(key, i)); 0415 const QIcon icon = cs->icon(key, i); 0416 if (!icon.isNull()) { 0417 setIcon(i, icon); 0418 } 0419 if (ds) { 0420 setForeground(i, QBrush(ds->keyForeground(key, foreground(i).color()))); 0421 setBackground(i, QBrush(ds->keyBackground(key, background(i).color()))); 0422 setFont(i, ds->keyFont(key, font(i))); 0423 } 0424 } 0425 } 0426 0427 QString KeyListViewItem::toolTip(int col) const 0428 { 0429 return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString(); 0430 } 0431 0432 bool KeyListViewItem::operator<(const QTreeWidgetItem &other) const 0433 { 0434 if (other.type() != RTTI || !listView() || !listView()->columnStrategy()) { 0435 return QTreeWidgetItem::operator<(other); 0436 } 0437 const auto that = static_cast<const KeyListViewItem *>(&other); 0438 return listView()->columnStrategy()->compare(this->key(), that->key(), treeWidget()->sortColumn()) < 0; 0439 } 0440 0441 void KeyListViewItem::takeItem(QTreeWidgetItem *qlvi) 0442 { 0443 // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListViewItem::takeItem(" << qlvi <<" )"; 0444 if (auto *item = lvi_cast<KeyListViewItem>(qlvi)) { 0445 listView()->deregisterItem(item); 0446 } 0447 takeChild(indexOfChild(qlvi)); 0448 } 0449 0450 // 0451 // 0452 // ColumnStrategy 0453 // 0454 // 0455 0456 KeyListView::ColumnStrategy::~ColumnStrategy() 0457 { 0458 } 0459 0460 int KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const 0461 { 0462 return QString::localeAwareCompare(text(key1, col), text(key2, col)); 0463 } 0464 0465 int KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const 0466 { 0467 return fm.horizontalAdvance(title(col)) * 2; 0468 } 0469 0470 QString KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const 0471 { 0472 return text(key, col); 0473 } 0474 0475 // 0476 // 0477 // DisplayStrategy 0478 // 0479 // 0480 0481 KeyListView::DisplayStrategy::~DisplayStrategy() 0482 { 0483 } 0484 0485 // font 0486 QFont KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const 0487 { 0488 return font; 0489 } 0490 0491 // foreground 0492 QColor KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const 0493 { 0494 return fg; 0495 } 0496 0497 // background 0498 QColor KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const 0499 { 0500 return bg; 0501 } 0502 0503 // 0504 // 0505 // Collection of covariant return reimplementations of QListView(Item) 0506 // members: 0507 // 0508 // 0509 0510 KeyListView *KeyListViewItem::listView() const 0511 { 0512 return static_cast<KeyListView *>(QTreeWidgetItem::treeWidget()); 0513 } 0514 0515 KeyListViewItem *KeyListViewItem::nextSibling() const 0516 { 0517 if (parent()) { 0518 const int myIndex = parent()->indexOfChild(const_cast<KeyListViewItem *>(this)); 0519 return static_cast<KeyListViewItem *>(parent()->child(myIndex + 1)); 0520 } 0521 const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast<KeyListViewItem *>(this)); 0522 return static_cast<KeyListViewItem *>(treeWidget()->topLevelItem(myIndex + 1)); 0523 } 0524 0525 KeyListViewItem *KeyListView::firstChild() const 0526 { 0527 return static_cast<KeyListViewItem *>(topLevelItem(0)); 0528 } 0529 0530 KeyListViewItem *KeyListView::selectedItem() const 0531 { 0532 QList<KeyListViewItem *> selection = selectedItems(); 0533 if (selection.isEmpty()) { 0534 return nullptr; 0535 } 0536 return selection.first(); 0537 } 0538 0539 QList<KeyListViewItem *> KeyListView::selectedItems() const 0540 { 0541 QList<KeyListViewItem *> result; 0542 const auto selectedItems = QTreeWidget::selectedItems(); 0543 for (QTreeWidgetItem *selectedItem : selectedItems) { 0544 if (auto *i = lvi_cast<KeyListViewItem>(selectedItem)) { 0545 result.append(i); 0546 } 0547 } 0548 return result; 0549 } 0550 0551 bool KeyListView::isMultiSelection() const 0552 { 0553 return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection; 0554 } 0555 0556 void KeyListView::keyPressEvent(QKeyEvent *event) 0557 { 0558 if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { 0559 if (selectedItem()) { 0560 slotEmitReturnPressed(selectedItem()); 0561 } 0562 } 0563 QTreeView::keyPressEvent(event); 0564 } 0565 0566 #include "moc_keylistview.cpp"