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
29using namespace Kleo;
30
31static const int updateDelayMilliSecs = 500;
32
33class Q_DECL_HIDDEN KeyListView::KeyListViewPrivate
34{
35public:
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:
48static 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};
65static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements;
66
67KeyListView::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
103KeyListView::~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
118void 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 }
125}
126
127void 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
140void 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
152void 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) {
175 }
176 d->keyBuffer.clear();
177}
178
179void KeyListView::clear()
180{
181 d->updateTimer->stop();
182 d->keyBuffer.clear();
183 while (QTreeWidgetItem *item = topLevelItem(0)) {
184 delete item;
185 }
187}
188
189void 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
201void 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
223void 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
243void 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...
256 parent->addChild(cur);
257 parent->setExpanded(true);
258 }
259 }
260}
261
262void 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
282KeyListViewItem *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
294void 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
310void 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
317void KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item)
318{
319 if (!item || lvi_cast<KeyListViewItem>(item)) {
320 Q_EMIT returnPressed(static_cast<KeyListViewItem *>(item));
321 }
322}
323
324void KeyListView::slotEmitSelectionChanged()
325{
326 Q_EMIT selectionChanged(selectedItem());
327}
328
329void 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
343KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key)
344 : QTreeWidgetItem(parent, RTTI)
345{
346 Q_ASSERT(parent);
347 setKey(key);
348}
349
350KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key)
351 : QTreeWidgetItem(parent, after, RTTI)
352{
353 Q_ASSERT(parent);
354 setKey(key);
355}
356
357KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key)
358 : QTreeWidgetItem(parent, RTTI)
359{
360 Q_ASSERT(parent && parent->listView());
361 setKey(key);
362}
363
364KeyListViewItem::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
371KeyListViewItem::~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
389void 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
427QString KeyListViewItem::toolTip(int col) const
428{
429 return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString();
430}
431
432bool 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
441void 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
456KeyListView::ColumnStrategy::~ColumnStrategy()
457{
458}
459
460int 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
465int KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const
466{
467 return fm.horizontalAdvance(title(col)) * 2;
468}
469
470QString 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
481KeyListView::DisplayStrategy::~DisplayStrategy()
482{
483}
484
485// font
486QFont KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const
487{
488 return font;
489}
490
491// foreground
492QColor KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const
493{
494 return fg;
495}
496
497// background
498QColor 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
510KeyListView *KeyListViewItem::listView() const
511{
512 return static_cast<KeyListView *>(QTreeWidgetItem::treeWidget());
513}
514
515KeyListViewItem *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
525KeyListViewItem *KeyListView::firstChild() const
526{
527 return static_cast<KeyListViewItem *>(topLevelItem(0));
528}
529
530KeyListViewItem *KeyListView::selectedItem() const
531{
532 QList<KeyListViewItem *> selection = selectedItems();
533 if (selection.isEmpty()) {
534 return nullptr;
535 }
536 return selection.first();
537}
538
539QList<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
551bool KeyListView::isMultiSelection() const
552{
554}
555
556void 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"
Q_SCRIPTABLE Q_NOREPLY void start()
QWidget * viewport() const const
bool isEmpty() const const
int horizontalAdvance(QChar ch) const const
bool isNull() const const
void append(QList< T > &&value)
T & first()
bool isEmpty() const const
Q_EMITQ_EMIT
QObject * parent() const const
int localeAwareCompare(QStringView s1, QStringView s2)
CustomContextMenu
AccessibleTextRole
Key_Return
typedef WindowFlags
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
virtual void keyPressEvent(QKeyEvent *event) override
void addTopLevelItem(QTreeWidgetItem *item)
void clear()
virtual bool event(QEvent *e) override
int indexOfTopLevelItem(QTreeWidgetItem *item) const const
QTreeWidgetItem * itemAt(const QPoint &p) const const
QList< QTreeWidgetItem * > selectedItems() const const
QTreeWidgetItem * takeTopLevelItem(int index)
QTreeWidgetItem * topLevelItem(int index) const const
QBrush background(int column) const const
QTreeWidgetItem * child(int index) const const
int childCount() const const
QFont font(int column) const const
QBrush foreground(int column) const const
QIcon icon(int column) const const
int indexOfChild(QTreeWidgetItem *child) const const
virtual bool operator<(const QTreeWidgetItem &other) const const
QTreeWidgetItem * parent() const const
void setBackground(int column, const QBrush &brush)
virtual void setData(int column, int role, const QVariant &value)
void setFont(int column, const QFont &font)
void setForeground(int column, const QBrush &brush)
void setIcon(int column, const QIcon &icon)
void setText(int column, const QString &text)
void setToolTip(int column, const QString &toolTip)
QTreeWidgetItem * takeChild(int index)
QTreeWidget * treeWidget() const const
int type() const const
QPoint mapToGlobal(const QPoint &pos) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.