Libkleo

keylistview.cpp
1 /*
2  keylistview.cpp
3 
4  This file is part of libkleopatra, the KDE keymanagement library
5  SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6 
7  SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include <config-libkleo.h>
11 
12 #include "keylistview.h"
13 
14 #include <kleo_ui_debug.h>
15 
16 #include <QColor>
17 #include <QFont>
18 #include <QFontMetrics>
19 #include <QKeyEvent>
20 #include <QPoint>
21 #include <QTimer>
22 #include <QToolTip>
23 
24 #include <gpgme++/key.h>
25 
26 #include <map>
27 #include <vector>
28 
29 using namespace Kleo;
30 
31 static const int updateDelayMilliSecs = 500;
32 
33 class Q_DECL_HIDDEN KeyListView::KeyListViewPrivate
34 {
35 public:
36  KeyListViewPrivate()
37  : updateTimer(nullptr)
38  {
39  }
40 
41  std::vector<GpgME::Key> keyBuffer;
42  QTimer *updateTimer = nullptr;
43  std::map<QByteArray, KeyListViewItem *> itemMap;
44 };
45 
46 // a list of signals where we want to replace QListViewItem with
47 // Kleo:KeyListViewItem:
48 static const struct {
49  const char *source;
50  const char *target;
51 } signalReplacements[] = {
52  {
53  SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)),
54  SLOT(slotEmitDoubleClicked(QTreeWidgetItem *, int)),
55  },
56  {
57  SIGNAL(itemSelectionChanged()),
58  SLOT(slotEmitSelectionChanged()),
59  },
60  {
61  SIGNAL(customContextMenuRequested(QPoint)),
62  SLOT(slotEmitContextMenu(QPoint)),
63  },
64 };
65 static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements;
66 
67 KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f)
68  : TreeWidget(parent)
69  , mColumnStrategy(columnStrategy)
70  , mDisplayStrategy(displayStrategy)
71  , mHierarchical(false)
72  , d(new KeyListViewPrivate())
73 {
74  setWindowFlags(f);
75  setContextMenuPolicy(Qt::CustomContextMenu);
76 
77  d->updateTimer = new QTimer(this);
78  d->updateTimer->setSingleShot(true);
79  connect(d->updateTimer, &QTimer::timeout, this, &KeyListView::slotUpdateTimeout);
80  if (!columnStrategy) {
81  qCWarning(KLEO_UI_LOG) << "Kleo::KeyListView: need a column strategy to work with!";
82  return;
83  }
84 
85  const QFontMetrics fm = fontMetrics();
86 
87  for (int col = 0; !columnStrategy->title(col).isEmpty(); ++col) {
88  headerItem()->setText(col, columnStrategy->title(col));
89  header()->resizeSection(col, columnStrategy->width(col, fm));
90  header()->setSectionResizeMode(col, columnStrategy->resizeMode(col));
91  }
92 
93  setAllColumnsShowFocus(false);
94 
95  for (int i = 0; i < numSignalReplacements; ++i) {
96  connect(this, signalReplacements[i].source, signalReplacements[i].target);
97  }
98 
99  this->setToolTip(QString());
100  viewport()->setToolTip(QString()); // make double sure :)
101 }
102 
103 KeyListView::~KeyListView()
104 {
105  d->updateTimer->stop();
106  // need to clear here, since in ~QListView, our children won't have
107  // a valid listView() pointing to us anymore, and their dtors try to
108  // unregister from us.
109  clear();
110  Q_ASSERT(d->itemMap.size() == 0);
111  // need to delete the tooltip ourselves, as ~QToolTip isn't virtual :o
112  delete mColumnStrategy;
113  mColumnStrategy = nullptr;
114  delete mDisplayStrategy;
115  mDisplayStrategy = nullptr;
116 }
117 
118 void KeyListView::takeItem(QTreeWidgetItem *qlvi)
119 {
120  // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )";
121  if (auto *item = lvi_cast<KeyListViewItem>(qlvi)) {
122  deregisterItem(item);
123  }
124  takeTopLevelItem(indexOfTopLevelItem(qlvi));
125 }
126 
127 void KeyListView::setHierarchical(bool hier)
128 {
129  if (hier == mHierarchical) {
130  return;
131  }
132  mHierarchical = hier;
133  if (hier) {
134  gatherScattered();
135  } else {
136  scatterGathered(firstChild());
137  }
138 }
139 
140 void KeyListView::slotAddKey(const GpgME::Key &key)
141 {
142  if (key.isNull()) {
143  return;
144  }
145 
146  d->keyBuffer.push_back(key);
147  if (!d->updateTimer->isActive()) {
148  d->updateTimer->start(updateDelayMilliSecs);
149  }
150 }
151 
152 void KeyListView::slotUpdateTimeout()
153 {
154  if (d->keyBuffer.empty()) {
155  return;
156  }
157 
158  const bool wasUpdatesEnabled = viewport()->updatesEnabled();
159  if (wasUpdatesEnabled) {
160  viewport()->setUpdatesEnabled(false);
161  }
162  qCDebug(KLEO_UI_LOG) << "Kleo::KeyListView::slotUpdateTimeout(): processing" << d->keyBuffer.size() << "items en block";
163  if (hierarchical()) {
164  for (std::vector<GpgME::Key>::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) {
165  doHierarchicalInsert(*it);
166  }
167  gatherScattered();
168  } else {
169  for (std::vector<GpgME::Key>::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) {
170  (void)new KeyListViewItem(this, *it);
171  }
172  }
173  if (wasUpdatesEnabled) {
174  viewport()->setUpdatesEnabled(true);
175  }
176  d->keyBuffer.clear();
177 }
178 
179 void KeyListView::clear()
180 {
181  d->updateTimer->stop();
182  d->keyBuffer.clear();
183  while (QTreeWidgetItem *item = topLevelItem(0)) {
184  delete item;
185  }
187 }
188 
189 void KeyListView::registerItem(KeyListViewItem *item)
190 {
191  // qCDebug(KLEO_UI_LOG) <<"registerItem(" << item <<" )";
192  if (!item) {
193  return;
194  }
195  const QByteArray fpr = item->key().primaryFingerprint();
196  if (!fpr.isEmpty()) {
197  d->itemMap.insert(std::make_pair(fpr, item));
198  }
199 }
200 
201 void KeyListView::deregisterItem(const KeyListViewItem *item)
202 {
203  // qCDebug(KLEO_UI_LOG) <<"deregisterItem( KeyLVI:" << item <<" )";
204  if (!item) {
205  return;
206  }
207  auto it = d->itemMap.find(item->key().primaryFingerprint());
208  if (it == d->itemMap.end()) {
209  return;
210  }
211  // This Q_ASSERT triggers, though it shouldn't. Print some more
212  // information when it happens.
213  // Q_ASSERT( it->second == item );
214  if (it->second != item) {
215  qCWarning(KLEO_UI_LOG) << "deregisterItem:"
216  << "item " << item->key().primaryFingerprint() //
217  << "it->second" << (it->second ? it->second->key().primaryFingerprint() : "is null");
218  return;
219  }
220  d->itemMap.erase(it);
221 }
222 
223 void KeyListView::doHierarchicalInsert(const GpgME::Key &key)
224 {
225  const QByteArray fpr = key.primaryFingerprint();
226  if (fpr.isEmpty()) {
227  return;
228  }
229  KeyListViewItem *item = nullptr;
230  if (!key.isRoot()) {
231  if (KeyListViewItem *parent = itemByFingerprint(key.chainID())) {
232  item = new KeyListViewItem(parent, key);
233  parent->setExpanded(true);
234  }
235  }
236  if (!item) {
237  item = new KeyListViewItem(this, key); // top-level (for now)
238  }
239 
240  d->itemMap.insert(std::make_pair(fpr, item));
241 }
242 
243 void KeyListView::gatherScattered()
244 {
245  KeyListViewItem *item = firstChild();
246  while (item) {
247  KeyListViewItem *cur = item;
248  item = item->nextSibling();
249  if (cur->key().isRoot()) {
250  continue;
251  }
252  if (KeyListViewItem *parent = itemByFingerprint(cur->key().chainID())) {
253  // found a new parent...
254  // ### todo: optimize by suppressing removing/adding the item to the itemMap...
255  takeTopLevelItem(indexOfTopLevelItem(cur));
256  parent->addChild(cur);
257  parent->setExpanded(true);
258  }
259  }
260 }
261 
262 void KeyListView::scatterGathered(KeyListViewItem *start)
263 {
264  KeyListViewItem *item = start;
265  while (item) {
266  KeyListViewItem *cur = item;
267  item = item->nextSibling();
268 
269  scatterGathered(lvi_cast<KeyListViewItem>(cur->child(0)));
270  Q_ASSERT(cur->childCount() == 0);
271 
272  // ### todo: optimize by suppressing removing/adding the item to the itemMap...
273  if (cur->parent()) {
274  static_cast<KeyListViewItem *>(cur->parent())->takeItem(cur);
275  } else {
276  takeItem(cur);
277  }
278  addTopLevelItem(cur);
279  }
280 }
281 
282 KeyListViewItem *KeyListView::itemByFingerprint(const QByteArray &s) const
283 {
284  if (s.isEmpty()) {
285  return nullptr;
286  }
287  const std::map<QByteArray, KeyListViewItem *>::const_iterator it = d->itemMap.find(s);
288  if (it == d->itemMap.end()) {
289  return nullptr;
290  }
291  return it->second;
292 }
293 
294 void KeyListView::slotRefreshKey(const GpgME::Key &key)
295 {
296  const char *fpr = key.primaryFingerprint();
297  if (!fpr) {
298  return;
299  }
300  if (KeyListViewItem *item = itemByFingerprint(fpr)) {
301  item->setKey(key);
302  } else {
303  // none found -> add it
304  slotAddKey(key);
305  }
306 }
307 
308 // slots for the emission of covariant Q_SIGNALS:
309 
310 void KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col)
311 {
312  if (!item || lvi_cast<KeyListViewItem>(item)) {
313  Q_EMIT doubleClicked(static_cast<KeyListViewItem *>(item), col);
314  }
315 }
316 
317 void KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item)
318 {
319  if (!item || lvi_cast<KeyListViewItem>(item)) {
320  Q_EMIT returnPressed(static_cast<KeyListViewItem *>(item));
321  }
322 }
323 
324 void KeyListView::slotEmitSelectionChanged()
325 {
326  Q_EMIT selectionChanged(selectedItem());
327 }
328 
329 void KeyListView::slotEmitContextMenu(const QPoint &pos)
330 {
331  QTreeWidgetItem *item = itemAt(pos);
332  if (!item || lvi_cast<KeyListViewItem>(item)) {
333  Q_EMIT contextMenu(static_cast<KeyListViewItem *>(item), viewport()->mapToGlobal(pos));
334  }
335 }
336 
337 //
338 //
339 // KeyListViewItem
340 //
341 //
342 
343 KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key)
344  : QTreeWidgetItem(parent, RTTI)
345 {
346  Q_ASSERT(parent);
347  setKey(key);
348 }
349 
350 KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key)
351  : QTreeWidgetItem(parent, after, RTTI)
352 {
353  Q_ASSERT(parent);
354  setKey(key);
355 }
356 
357 KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key)
358  : QTreeWidgetItem(parent, RTTI)
359 {
360  Q_ASSERT(parent && parent->listView());
361  setKey(key);
362 }
363 
364 KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key)
365  : QTreeWidgetItem(parent, after, RTTI)
366 {
367  Q_ASSERT(parent && parent->listView());
368  setKey(key);
369 }
370 
371 KeyListViewItem::~KeyListViewItem()
372 {
373  // delete the children first... When children are deleted in the
374  // QLVI dtor, they don't have listView() anymore, thus they don't
375  // call deregister( this ), leading to stale entries in the
376  // itemMap...
377  while (QTreeWidgetItem *item = child(0)) {
378  delete item;
379  }
380  // better do this here, too, since deletion is top-down and thus
381  // we're deleted when our parent item is no longer a
382  // KeyListViewItem, but a mere QListViewItem, so our takeItem()
383  // overload is gone by that time...
384  if (KeyListView *lv = listView()) {
385  lv->deregisterItem(this);
386  }
387 }
388 
389 void KeyListViewItem::setKey(const GpgME::Key &key)
390 {
391  KeyListView *lv = listView();
392  if (lv) {
393  lv->deregisterItem(this);
394  }
395  mKey = key;
396  if (lv) {
397  lv->registerItem(this);
398  }
399 
400  // the ColumnStrategy operations might be very slow, so cache their
401  // result here, where we're non-const :)
402  const KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr;
403  if (!cs) {
404  return;
405  }
406  const KeyListView::DisplayStrategy *ds = lv->displayStrategy();
407  const int numCols = lv ? lv->columnCount() : 0;
408  for (int i = 0; i < numCols; ++i) {
409  setText(i, cs->text(key, i));
410  const auto accessibleText = cs->accessibleText(key, i);
411  if (!accessibleText.isEmpty()) {
412  setData(i, Qt::AccessibleTextRole, accessibleText);
413  }
414  setToolTip(i, cs->toolTip(key, i));
415  const QIcon icon = cs->icon(key, i);
416  if (!icon.isNull()) {
417  setIcon(i, icon);
418  }
419  if (ds) {
420  setForeground(i, QBrush(ds->keyForeground(key, foreground(i).color())));
421  setBackground(i, QBrush(ds->keyBackground(key, background(i).color())));
422  setFont(i, ds->keyFont(key, font(i)));
423  }
424  }
425 }
426 
427 QString KeyListViewItem::toolTip(int col) const
428 {
429  return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString();
430 }
431 
432 bool KeyListViewItem::operator<(const QTreeWidgetItem &other) const
433 {
434  if (other.type() != RTTI || !listView() || !listView()->columnStrategy()) {
435  return QTreeWidgetItem::operator<(other);
436  }
437  const auto that = static_cast<const KeyListViewItem *>(&other);
438  return listView()->columnStrategy()->compare(this->key(), that->key(), treeWidget()->sortColumn()) < 0;
439 }
440 
441 void KeyListViewItem::takeItem(QTreeWidgetItem *qlvi)
442 {
443  // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListViewItem::takeItem(" << qlvi <<" )";
444  if (auto *item = lvi_cast<KeyListViewItem>(qlvi)) {
445  listView()->deregisterItem(item);
446  }
447  takeChild(indexOfChild(qlvi));
448 }
449 
450 //
451 //
452 // ColumnStrategy
453 //
454 //
455 
456 KeyListView::ColumnStrategy::~ColumnStrategy()
457 {
458 }
459 
460 int KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const
461 {
462  return QString::localeAwareCompare(text(key1, col), text(key2, col));
463 }
464 
465 int KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const
466 {
467  return fm.horizontalAdvance(title(col)) * 2;
468 }
469 
470 QString KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const
471 {
472  return text(key, col);
473 }
474 
475 //
476 //
477 // DisplayStrategy
478 //
479 //
480 
481 KeyListView::DisplayStrategy::~DisplayStrategy()
482 {
483 }
484 
485 // font
486 QFont KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const
487 {
488  return font;
489 }
490 
491 // foreground
492 QColor KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const
493 {
494  return fg;
495 }
496 
497 // background
498 QColor KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const
499 {
500  return bg;
501 }
502 
503 //
504 //
505 // Collection of covariant return reimplementations of QListView(Item)
506 // members:
507 //
508 //
509 
510 KeyListView *KeyListViewItem::listView() const
511 {
512  return static_cast<KeyListView *>(QTreeWidgetItem::treeWidget());
513 }
514 
515 KeyListViewItem *KeyListViewItem::nextSibling() const
516 {
517  if (parent()) {
518  const int myIndex = parent()->indexOfChild(const_cast<KeyListViewItem *>(this));
519  return static_cast<KeyListViewItem *>(parent()->child(myIndex + 1));
520  }
521  const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast<KeyListViewItem *>(this));
522  return static_cast<KeyListViewItem *>(treeWidget()->topLevelItem(myIndex + 1));
523 }
524 
525 KeyListViewItem *KeyListView::firstChild() const
526 {
527  return static_cast<KeyListViewItem *>(topLevelItem(0));
528 }
529 
530 KeyListViewItem *KeyListView::selectedItem() const
531 {
532  QList<KeyListViewItem *> selection = selectedItems();
533  if (selection.isEmpty()) {
534  return nullptr;
535  }
536  return selection.first();
537 }
538 
539 QList<KeyListViewItem *> KeyListView::selectedItems() const
540 {
542  const auto selectedItems = QTreeWidget::selectedItems();
543  for (QTreeWidgetItem *selectedItem : selectedItems) {
544  if (auto *i = lvi_cast<KeyListViewItem>(selectedItem)) {
545  result.append(i);
546  }
547  }
548  return result;
549 }
550 
551 bool KeyListView::isMultiSelection() const
552 {
553  return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection;
554 }
555 
556 void KeyListView::keyPressEvent(QKeyEvent *event)
557 {
558  if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
559  if (selectedItem()) {
560  slotEmitReturnPressed(selectedItem());
561  }
562  }
564 }
565 
566 #include "moc_keylistview.cpp"
void append(const T &value)
T & first()
int type() const const
AccessibleTextRole
int horizontalAdvance(const QString &text, int len) const const
Q_SCRIPTABLE Q_NOREPLY void start()
CustomContextMenu
typedef WindowFlags
void clear()
bool isNull() const const
QTreeWidget * treeWidget() const const
void timeout()
bool isEmpty() const const
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Key_Return
QList< QTreeWidgetItem * > selectedItems() const const
A tree widget that allows accessible column by column keyboard navigation and that has customizable c...
Definition: treewidget.h:28
QAction * clear(const QObject *recvr, const char *slot, QObject *parent)
int localeAwareCompare(const QString &other) const const
bool isEmpty() const const
virtual void keyPressEvent(QKeyEvent *event) override
virtual bool operator<(const QTreeWidgetItem &other) const const
MESSAGECORE_EXPORT KMime::Content * firstChild(const KMime::Content *node)
QByteArray & insert(int i, char ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.