summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zaslavsky <diazona@ellipsix.net>2016-07-17 13:39:12 (GMT)
committerRolf Eike Beer <kde@opensource.sf-tec.de>2016-08-20 07:07:06 (GMT)
commit95c6901392c17ef174a63188447f58d02338aeef (patch)
tree13f03bc0d416b0d24e7292b10f164c51f4621d01
parent0779334e4cb13bed796b8f3c22617179972d6105 (diff)
Show only valid keys in search results
This adds - fields to SearchResult for expiration and revocation status - a proxy model to filter only valid keys from the search results - a checkbox to the keyserver search result dialog to toggle validity filtering BUG:254779 REVIEW:128701
-rw-r--r--keyservers.cpp57
-rw-r--r--keyservers.h6
-rw-r--r--model/kgpgsearchresultmodel.cpp125
-rw-r--r--model/kgpgsearchresultmodel.h102
-rw-r--r--searchres.ui10
5 files changed, 249 insertions, 51 deletions
diff --git a/keyservers.cpp b/keyservers.cpp
index 808442b..081d00f 100644
--- a/keyservers.cpp
+++ b/keyservers.cpp
@@ -36,15 +36,14 @@ KeyServer::KeyServer(QWidget *parent, KGpgItemModel *model, const bool autoclose
m_searchproc(Q_NULLPTR),
page(new keyServerWidget()),
m_listpop(Q_NULLPTR),
- m_resultmodel(Q_NULLPTR),
m_itemmodel(new KeyListProxyModel(this, KeyListProxyModel::SingleColumnIdFirst))
{
setWindowTitle(i18n("Key Server"));
m_autoclose = autoclose;
- m_filtermodel.setSortCaseSensitivity(Qt::CaseInsensitive);
- m_filtermodel.setDynamicSortFilter(true);
- m_filtermodel.setFilterKeyColumn(0);
+ m_resultmodel.setSortCaseSensitivity(Qt::CaseInsensitive);
+ m_resultmodel.setDynamicSortFilter(true);
+ m_resultmodel.setFilterKeyColumn(0);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
setLayout(mainLayout);
@@ -194,11 +193,9 @@ void KeyServer::slotSearch()
if (m_searchproc)
return;
- if (m_resultmodel != Q_NULLPTR)
- m_resultmodel->deleteLater();
- m_resultmodel = new KGpgSearchResultModel(this);
- m_filtermodel.setSourceModel(m_resultmodel);
- m_filtermodel.setFilterRegExp(QRegExp());
+ m_resultmodel.resetSourceModel();
+ m_resultmodel.setFilterRegExp(QRegExp());
+ m_resultmodel.setFilterByValidity(true);
m_dialogserver = new QDialog(this);
m_dialogserver->setWindowTitle(i18n("Import Key From Keyserver"));
@@ -209,12 +206,15 @@ void KeyServer::slotSearch()
m_listpop = new searchRes(m_dialogserver);
m_listpop->buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("&Import"));
m_listpop->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
- m_listpop->kLVsearch->setModel(&m_filtermodel);
+ m_listpop->kLVsearch->setModel(&m_resultmodel);
m_listpop->kLVsearch->setColumnWidth(0, 180);
+ m_listpop->validFilterCheckbox->setChecked(m_resultmodel.filterByValidity());
m_listpop->statusText->setText(i18n("Connecting to the server..."));
connect(m_listpop->filterEdit, &QLineEdit::textChanged, this, &KeyServer::slotSetFilterString);
connect(m_listpop->kLVsearch->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KeyServer::transferKeyID);
+ connect(m_listpop->validFilterCheckbox, &QCheckBox::toggled, &m_resultmodel, &KGpgSearchResultModel::setFilterByValidity);
+ connect(m_listpop->validFilterCheckbox, &QCheckBox::toggled, this, &KeyServer::slotUpdateLabelOnFilterChange);
connect(m_listpop->buttonBox, &QDialogButtonBox::accepted, this, &KeyServer::slotPreImport);
connect(m_listpop->kLVsearch, &QTreeView::activated, m_dialogserver, &QDialog::accepted);
connect(m_listpop->buttonBox, &QDialogButtonBox::rejected, this, &KeyServer::handleQuit);
@@ -237,7 +237,7 @@ void KeyServer::slotSearch()
m_searchproc = new KGpgKeyserverSearchTransaction(this, keyserv, page->qLEimportid->text().simplified(),
true, proxy);
connect(m_searchproc, &KGpgKeyserverSearchTransaction::done, this, &KeyServer::slotSearchResult);
- connect(m_searchproc, &KGpgKeyserverSearchTransaction::newKey, m_resultmodel, &KGpgSearchResultModel::slotAddKey);
+ connect(m_searchproc, &KGpgKeyserverSearchTransaction::newKey, &m_resultmodel, &KGpgSearchResultModel::slotAddKey);
m_searchproc->start();
QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
@@ -262,15 +262,12 @@ void KeyServer::slotSearchResult(int result)
m_listpop->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
- const int keys = m_resultmodel->rowCount(QModelIndex());
-
+ const int keys = m_resultmodel.sourceRowCount(QModelIndex());
if (keys > 0) {
- m_listpop->statusText->setText(i18np("Found 1 matching key", "Found %1 matching keys", keys));
- m_listpop->kLVsearch->selectionModel()->setCurrentIndex(m_resultmodel->index(0, 0),
+ m_listpop->kLVsearch->selectionModel()->setCurrentIndex(m_resultmodel.index(0, 0),
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
- } else {
- m_listpop->statusText->setText(i18n("No matching keys found"));
}
+ slotUpdateLabelOnFilterChange();
}
void KeyServer::slotSetText(const QString &text)
@@ -307,7 +304,7 @@ void KeyServer::transferKeyID()
QSet<QString> ids;
foreach (const QModelIndex &index, m_listpop->kLVsearch->selectionModel()->selectedIndexes())
- ids << m_resultmodel->idForIndex(m_filtermodel.mapToSource(index));
+ ids << m_resultmodel.idForIndex(index);
const QStringList idlist(ids.toList());
m_listpop->qLEID->setText(idlist.join( QLatin1String( " " )));
@@ -367,5 +364,27 @@ void KeyServer::slotSetKeyserver(const QString &server)
void KeyServer::slotSetFilterString(const QString &expression)
{
- m_filtermodel.setFilterRegExp(QRegExp(expression, Qt::CaseInsensitive, QRegExp::RegExp2));
+ m_resultmodel.setFilterRegExp(QRegExp(expression, Qt::CaseInsensitive, QRegExp::RegExp2));
+ slotUpdateLabelOnFilterChange();
+}
+
+void KeyServer::slotUpdateLabelOnFilterChange()
+{
+ const int keys = m_resultmodel.sourceRowCount(QModelIndex());
+ const int keysShown = m_resultmodel.rowCount(QModelIndex());
+ Q_ASSERT(keysShown <= keys);
+
+ if (keys == 0) {
+ m_listpop->statusText->setText(i18n("No matching keys found"));
+ } else {
+ if (keysShown == keys) {
+ m_listpop->statusText->setText(i18np("Found 1 matching key", "Found %1 matching keys", keys));
+ } else {
+ if (keys == 1 && keysShown == 0) {
+ m_listpop->statusText->setText(i18n("Found 1 matching key (not shown)"));
+ } else {
+ m_listpop->statusText->setText(i18n("Found %1 matching keys (%2 shown)", keys, keysShown));
+ }
+ }
+ }
}
diff --git a/keyservers.h b/keyservers.h
index 6a6a5f9..f619ea4 100644
--- a/keyservers.h
+++ b/keyservers.h
@@ -20,13 +20,13 @@
#include <QDialog>
#include "core/kgpgkey.h"
+#include "model/kgpgsearchresultmodel.h"
#include "ui_searchres.h"
#include "ui_keyserver.h"
class KGpgKeyserverSearchTransaction;
class KeyListProxyModel;
class KGpgItemModel;
-class KGpgSearchResultModel;
class keyServerWidget : public QWidget, public Ui::keyServerWidget
{
@@ -111,6 +111,7 @@ private slots:
void slotSearchResult(int result);
void slotSearch();
void slotSetFilterString(const QString &expression);
+ void slotUpdateLabelOnFilterChange();
private:
QString m_readmessage;
@@ -124,8 +125,7 @@ private:
bool m_autoclose;
QString expattr;
- KGpgSearchResultModel *m_resultmodel;
- QSortFilterProxyModel m_filtermodel;
+ KGpgSearchResultModel m_resultmodel;
KeyListProxyModel *m_itemmodel;
};
diff --git a/model/kgpgsearchresultmodel.cpp b/model/kgpgsearchresultmodel.cpp
index 5b1d939..31d15ed 100644
--- a/model/kgpgsearchresultmodel.cpp
+++ b/model/kgpgsearchresultmodel.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2009,2010,2013 Rolf Eike Beer <kde@opensource.sf-tec.de>
+ * 2016 David Zaslavsky <diazona@ellipsix.net>
*/
/***************************************************************************
@@ -39,6 +40,9 @@ public:
const QString &getName(const int index) const;
const QString &getEmail(const int index) const;
int getUidCount() const;
+ bool valid() const;
+ bool expired() const;
+ bool revoked() const;
QString m_fingerprint;
unsigned int m_uatCount;
@@ -46,6 +50,7 @@ public:
QVariant summary() const;
private:
QDateTime m_creation;
+ bool m_expired;
bool m_revoked;
unsigned int m_bits;
KgpgCore::KgpgKeyAlgo m_algo;
@@ -64,6 +69,7 @@ public:
SearchResult::SearchResult(const QString &line)
: m_validPub(false),
m_uatCount(0),
+ m_expired(false),
m_revoked(false),
m_bits(0)
{
@@ -79,6 +85,7 @@ SearchResult::SearchResult(const QString &line)
m_algo = KgpgCore::Convert::toAlgo(parts.at(2));
m_bits = parts.at(3).toUInt();
m_creation.setTime_t(parts.at(4).toULongLong());
+ m_expired = QDateTime::fromTime_t(parts.at(5).toULongLong()) <= QDateTime::currentDateTimeUtc();
m_revoked = (parts.at(6) == QLatin1String( "r" ));
m_validPub = true;
@@ -119,6 +126,24 @@ SearchResult::getUidCount() const
return m_emails.count();
}
+bool
+SearchResult::expired() const
+{
+ return m_expired;
+}
+
+bool
+SearchResult::revoked() const
+{
+ return m_revoked;
+}
+
+bool
+SearchResult::valid() const
+{
+ return !(revoked() || expired());
+}
+
QVariant
SearchResult::summary() const
{
@@ -173,12 +198,12 @@ KGpgSearchResultModelPrivate::urlDecode(const QString &line)
return QTextCodec::codecForName("utf8")->toUnicode(tmp);
}
-KGpgSearchResultModel::KGpgSearchResultModel(QObject *parent)
+KGpgSearchResultBackingModel::KGpgSearchResultBackingModel(QObject *parent)
: QAbstractItemModel(parent), d(new KGpgSearchResultModelPrivate())
{
}
-KGpgSearchResultModel::~KGpgSearchResultModel()
+KGpgSearchResultBackingModel::~KGpgSearchResultBackingModel()
{
delete d;
}
@@ -201,8 +226,8 @@ KGpgSearchResultModel::~KGpgSearchResultModel()
* which aren't going to disappear from memory at any moment.
*/
-KGpgSearchResultModel::NodeLevel
-KGpgSearchResultModel::nodeLevel(const QModelIndex &index)
+KGpgSearchResultBackingModel::NodeLevel
+KGpgSearchResultBackingModel::nodeLevel(const QModelIndex &index)
{
if (!index.isValid())
return ROOT_LEVEL;
@@ -213,7 +238,7 @@ KGpgSearchResultModel::nodeLevel(const QModelIndex &index)
}
SearchResult *
-KGpgSearchResultModel::resultForIndex(const QModelIndex &index) const
+KGpgSearchResultBackingModel::resultForIndex(const QModelIndex &index) const
{
switch (nodeLevel(index)) {
case KEY_LEVEL:
@@ -229,9 +254,8 @@ KGpgSearchResultModel::resultForIndex(const QModelIndex &index) const
}
}
-
QVariant
-KGpgSearchResultModel::data(const QModelIndex &index, int role) const
+KGpgSearchResultBackingModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@@ -297,7 +321,7 @@ KGpgSearchResultModel::data(const QModelIndex &index, int role) const
}
int
-KGpgSearchResultModel::columnCount(const QModelIndex &parent) const
+KGpgSearchResultBackingModel::columnCount(const QModelIndex &parent) const
{
switch (nodeLevel(parent)) {
case KEY_LEVEL:
@@ -315,7 +339,7 @@ KGpgSearchResultModel::columnCount(const QModelIndex &parent) const
}
QModelIndex
-KGpgSearchResultModel::index(int row, int column, const QModelIndex &parent) const
+KGpgSearchResultBackingModel::index(int row, int column, const QModelIndex &parent) const
{
switch (nodeLevel(parent)) {
case ATTRIBUTE_LEVEL:
@@ -342,7 +366,7 @@ KGpgSearchResultModel::index(int row, int column, const QModelIndex &parent) con
}
QModelIndex
-KGpgSearchResultModel::parent(const QModelIndex &index) const
+KGpgSearchResultBackingModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
@@ -362,7 +386,7 @@ KGpgSearchResultModel::parent(const QModelIndex &index) const
}
int
-KGpgSearchResultModel::rowCount(const QModelIndex &parent) const
+KGpgSearchResultBackingModel::rowCount(const QModelIndex &parent) const
{
switch (nodeLevel(parent)) {
case ROOT_LEVEL:
@@ -386,7 +410,7 @@ KGpgSearchResultModel::rowCount(const QModelIndex &parent) const
}
QVariant
-KGpgSearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const
+KGpgSearchResultBackingModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
@@ -405,7 +429,7 @@ KGpgSearchResultModel::headerData(int section, Qt::Orientation orientation, int
}
const QString &
-KGpgSearchResultModel::idForIndex(const QModelIndex &index) const
+KGpgSearchResultBackingModel::idForIndex(const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
@@ -420,7 +444,7 @@ KGpgSearchResultModel::idForIndex(const QModelIndex &index) const
}
void
-KGpgSearchResultModel::slotAddKey(const QStringList &lines)
+KGpgSearchResultBackingModel::slotAddKey(const QStringList &lines)
{
Q_ASSERT(!lines.isEmpty());
Q_ASSERT(lines.first().startsWith(QLatin1String("pub:")));
@@ -454,3 +478,76 @@ KGpgSearchResultModel::slotAddKey(const QStringList &lines)
endInsertRows();
}
}
+
+KGpgSearchResultModel::KGpgSearchResultModel(QObject *parent)
+ : QSortFilterProxyModel(parent),
+ m_filterByValidity(true)
+{
+ resetSourceModel();
+}
+
+KGpgSearchResultModel::~KGpgSearchResultModel()
+{
+}
+
+bool
+KGpgSearchResultModel::filterByValidity() const
+{
+ return m_filterByValidity;
+}
+
+const QString &
+KGpgSearchResultModel::idForIndex(const QModelIndex &index) const
+{
+ return static_cast<KGpgSearchResultBackingModel *>(sourceModel())->idForIndex(mapToSource(index));
+}
+
+int
+KGpgSearchResultModel::sourceRowCount(const QModelIndex &parent) const
+{
+ return sourceModel()->rowCount(parent);
+}
+
+void
+KGpgSearchResultModel::setFilterByValidity(bool filter)
+{
+ m_filterByValidity = filter;
+ invalidateFilter();
+}
+
+void
+KGpgSearchResultModel::setSourceModel(QAbstractItemModel *)
+{
+ Q_ASSERT(false);
+}
+
+void
+KGpgSearchResultModel::slotAddKey(const QStringList &key)
+{
+ static_cast<KGpgSearchResultBackingModel *>(sourceModel())->slotAddKey(key);
+}
+
+void
+KGpgSearchResultModel::resetSourceModel()
+{
+ QAbstractItemModel *oldSourceModel = sourceModel();
+ if (oldSourceModel != Q_NULLPTR)
+ oldSourceModel->deleteLater();
+ QSortFilterProxyModel::setSourceModel(new KGpgSearchResultBackingModel(this));
+}
+
+bool
+KGpgSearchResultModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ // first check the text filter, implemented in the superclass
+ if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
+ return false;
+ } else if (!filterByValidity()) {
+ // if the text filter matched and we're not hiding invalid keys, accept the row
+ return true;
+ }
+ // otherwise, validity filtering is enabled, so check whether the row is valid
+ KGpgSearchResultBackingModel *backingModel = static_cast<KGpgSearchResultBackingModel *>(sourceModel());
+ QModelIndex currentKeyIndex = backingModel->index(sourceRow, 0, sourceParent);
+ return backingModel->resultForIndex(currentKeyIndex)->valid();
+}
diff --git a/model/kgpgsearchresultmodel.h b/model/kgpgsearchresultmodel.h
index 3c34985..e583251 100644
--- a/model/kgpgsearchresultmodel.h
+++ b/model/kgpgsearchresultmodel.h
@@ -1,4 +1,5 @@
/* Copyright 2009,2010 Rolf Eike Beer <kde@opensource.sf-tec.de>
+ * 2016 David Zaslavsky <diazona@ellipsix.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -20,6 +21,7 @@
#define KGPGSEARCHRESULTMODEL_H
#include <QAbstractItemModel>
+#include <QSortFilterProxyModel>
#include <QStringList>
#include <kgpgcompiler.h>
@@ -28,24 +30,27 @@ class SearchResult;
class KGpgSearchResultModelPrivate;
/**
- * @brief Model of the results of a keyserver search
+ * @brief A model to store the results of a keyserver search.
*
* This model parses and stores the results of a search on a keyserver.
+ * It is never used directly, only by `KGpgSearchResultModel`.
*
* @author Rolf Eike Beer
+ * @author David Zaslavsky
*/
-class KGpgSearchResultModel : public QAbstractItemModel {
+class KGpgSearchResultBackingModel : public QAbstractItemModel {
+ // The moc complains if I put this class definition in the .cpp file
Q_OBJECT
public:
- explicit KGpgSearchResultModel(QObject *parent = Q_NULLPTR);
- ~KGpgSearchResultModel();
+ explicit KGpgSearchResultBackingModel(QObject *parent = Q_NULLPTR);
+ ~KGpgSearchResultBackingModel();
- virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
- virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
- virtual QModelIndex parent(const QModelIndex &index) const;
- virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
- QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ virtual QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
/**
* @brief get the key fingerprint for the given index
@@ -54,14 +59,11 @@ public:
*/
const QString &idForIndex(const QModelIndex &index) const;
-public slots:
- void slotAddKey(const QStringList &lines);
-
-private:
typedef enum {ROOT_LEVEL, KEY_LEVEL, ATTRIBUTE_LEVEL} NodeLevel;
+
/**
* @brief Returns the level corresponding to a `QModelIndex` associated
- * with this `KGpgSearchResultModel`.
+ * with this model.
*
* There are three levels of nodes. The top level, level 0, is the
* root node. Each first-level subnode corresponds to a key, and each
@@ -83,7 +85,77 @@ private:
*/
SearchResult *resultForIndex(const QModelIndex &index) const;
+public slots:
+ void slotAddKey(const QStringList &lines);
+
+private:
KGpgSearchResultModelPrivate * const d;
};
+/**
+ * @brief A model to parse, store, and display the results of
+ * a keyserver search.
+ *
+ * This model manages the results returned by a keyserver search.
+ * It is a proxy model, backed by a source model which parses
+ * and stores the list of keys yielded by the search. The proxy
+ * model exposes a sorted and/or filtered version of that list
+ * to the view. On top of the sorting and regexp-based filtering
+ * allowed by a basic `QSortFilterProxyModel`, this adds the
+ * ability to filter out invalid keys.
+ *
+ * Unlike a generic `QSortFilterProxyModel`, this manages its
+ * own source model internally. Don't set the source model with
+ * `setSourceModel()` yourself.
+ *
+ * @author David Zaslavsky
+ */
+class KGpgSearchResultModel : public QSortFilterProxyModel {
+ Q_OBJECT
+public:
+ explicit KGpgSearchResultModel(QObject *parent = Q_NULLPTR);
+ ~KGpgSearchResultModel();
+
+ bool filterByValidity() const;
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE;
+
+ /**
+ * @brief get the key fingerprint for the given index
+ * @param index valid index of any item in the model
+ * @return fingerprint of the corresponding key
+ */
+ const QString &idForIndex(const QModelIndex &index) const;
+
+ /**
+ * @brief Return the total number of rows in the source model.
+ */
+ int sourceRowCount(const QModelIndex &parent = QModelIndex()) const;
+
+ /**
+ * Don't use this. The filter model manages its own source
+ * internally. Use `resetSourceModel()` if you want to clear the
+ * source model, and `slotAddKey()` to populate it.
+ */
+ virtual void setSourceModel(QAbstractItemModel *sourceModel) Q_DECL_OVERRIDE;
+
+public slots:
+ /**
+ * @brief Control whether validity filtering of keys is enabled.
+ *
+ * @param filter `true` to hide expired/revoked keys, `false` to show them
+ */
+ void setFilterByValidity(bool filter);
+ /**
+ * @brief Adds a key to the underlying source model.
+ */
+ void slotAddKey(const QStringList &lines);
+ /**
+ * @brief Resets the source model to be empty.
+ */
+ void resetSourceModel();
+
+private:
+ bool m_filterByValidity;
+};
+
#endif
diff --git a/searchres.ui b/searchres.ui
index f1e2600..998711f 100644
--- a/searchres.ui
+++ b/searchres.ui
@@ -43,6 +43,16 @@
</layout>
</item>
<item>
+ <widget class="QCheckBox" name="validFilterCheckbox">
+ <property name="text">
+ <string>Show only valid keys</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QTreeView" name="kLVsearch">
<property name="rootIsDecorated">
<bool>true</bool>