Commit 95c69013 authored by David Zaslavsky's avatar David Zaslavsky Committed by Rolf Eike Beer

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
parent 0779334e
......@@ -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));
}
}
}
}
......@@ -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;
};
......
/*
* 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();
}
/* 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
......@@ -42,6 +42,16 @@
</item>
</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">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment