mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 10:52:53 +00:00
595 lines
17 KiB
C++
595 lines
17 KiB
C++
/* Copyright 2008,2009,2010,2012,2013 Rolf Eike Beer <kde@opensource.sf-tec.de>
|
|
* Copyright 2013 Thomas Fischer <fischer@unix-ag.uni-kl.de>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License or (at your option) version 3 or any later version
|
|
* accepted by the membership of KDE e.V. (or its successor approved
|
|
* by the membership of KDE e.V.), which shall act as a proxy
|
|
* defined in Section 14 of version 3 of the license.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "keylistproxymodel.h"
|
|
#include "model/kgpgitemnode.h"
|
|
#include "kgpgitemmodel.h"
|
|
#include "kgpgsettings.h"
|
|
#include "core/kgpgkey.h"
|
|
#include "core/images.h"
|
|
|
|
#include <KLocale>
|
|
#include <QDate>
|
|
|
|
using namespace KgpgCore;
|
|
|
|
class KeyListProxyModelPrivate {
|
|
KeyListProxyModel * const q_ptr;
|
|
|
|
Q_DECLARE_PUBLIC(KeyListProxyModel)
|
|
public:
|
|
KeyListProxyModelPrivate(KeyListProxyModel *parent, const KeyListProxyModel::DisplayMode mode);
|
|
|
|
bool lessThan(const KGpgNode *left, const KGpgNode *right, const int column) const;
|
|
bool nodeLessThan(const KGpgNode *left, const KGpgNode *right, const int column) const;
|
|
KGpgItemModel *m_model;
|
|
bool m_onlysecret;
|
|
bool m_encryptionKeys;
|
|
KgpgCore::KgpgKeyTrustFlag m_mintrust;
|
|
int m_previewsize;
|
|
int m_idLength;
|
|
KeyListProxyModel::DisplayMode m_displaymode;
|
|
int m_emailSorting;
|
|
|
|
QString reorderEmailComponents(const QString &emailAddress) const;
|
|
QVariant dataSingleColumn(const QModelIndex &index, int role, const KGpgNode *node) const;
|
|
QVariant dataMultiColumn(const QModelIndex &index, int role, const KGpgNode *node) const;
|
|
};
|
|
|
|
KeyListProxyModelPrivate::KeyListProxyModelPrivate(KeyListProxyModel *parent, const KeyListProxyModel::DisplayMode mode)
|
|
: q_ptr(parent),
|
|
m_model(NULL),
|
|
m_onlysecret(false),
|
|
m_encryptionKeys(false),
|
|
m_mintrust(TRUST_UNKNOWN),
|
|
m_previewsize(22),
|
|
m_idLength(8),
|
|
m_displaymode(mode),
|
|
m_emailSorting(KGpgSettings::emailSorting())
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Reverses the list's order (this modifies the list) and returns
|
|
* a string containing the reversed list's elements joined by a char.
|
|
*/
|
|
static QString reverseListAndJoinWithChar(const QStringList &list, const QChar &separator)
|
|
{
|
|
QString result = list.last();
|
|
for (int i = list.count() - 2; i >= 0; --i)
|
|
result.append(separator).append(list[i]);
|
|
return result;
|
|
}
|
|
|
|
QString
|
|
KeyListProxyModelPrivate::reorderEmailComponents(const QString &emailAddress) const
|
|
{
|
|
if (emailAddress.isEmpty())
|
|
return QString();
|
|
|
|
/// split email addresses at @
|
|
static const QChar charAt = QLatin1Char('@');
|
|
/// split domain at .
|
|
static const QChar charDot = QLatin1Char('.');
|
|
|
|
/// convert result to lower case to make sorting case-insensitive
|
|
QString result = emailAddress.toLower();
|
|
|
|
switch (m_emailSorting) {
|
|
case KGpgSettings::EnumEmailSorting::TLDfirst:
|
|
{
|
|
/// get components of an email address
|
|
/// john.doe@mail.kde.org becomes [john.doe, mail.kde.org]
|
|
const QStringList emailComponents = result.split(charAt);
|
|
if (emailComponents.count() != 2) /// expect an email address to contain exactly one @
|
|
break;
|
|
/// get components of a domain
|
|
/// mail.kde.org becomes [mail, kde, org]
|
|
const QString fqdn = emailComponents.last();
|
|
QStringList fqdnComponents = fqdn.split(charDot);
|
|
if (fqdnComponents.count() < 2) /// if domain consists of less than two components ...
|
|
return fqdn + charDot + emailComponents.first(); /// ... take shortcut
|
|
/// prepend localpart, will be last after list is reversed
|
|
fqdnComponents.insert(0, emailComponents.first());
|
|
/// reverse components of domain, result becomes e.g. org.kde.mail
|
|
/// with localpart already in the list it becomes org.kde.mail.john.doe
|
|
result = reverseListAndJoinWithChar(fqdnComponents, charDot);
|
|
break;
|
|
}
|
|
case KGpgSettings::EnumEmailSorting::DomainFirst:
|
|
{
|
|
/// get components of an email address
|
|
/// john.doe@mail.kde.org becomes [john.doe, mail.kde.org]
|
|
const QStringList emailComponents = result.split(charAt);
|
|
if (emailComponents.count() != 2) /// expect an email address to contain exactly one @
|
|
break;
|
|
/// get components of a domain
|
|
/// mail.kde.org becomes [mail, kde, org]
|
|
const QString fqdn = emailComponents.last();
|
|
QStringList fqdnComponents = fqdn.split(charDot);
|
|
if (fqdnComponents.count() < 2) /// if domain consists of less than two components ...
|
|
return fqdn + charDot + emailComponents.first(); /// ... take shortcut
|
|
/// reverse last two components of domain, becomes e.g. kde.org
|
|
/// TODO will fail for three-part domains like kde.org.uk
|
|
result = charDot + fqdnComponents.takeLast();
|
|
result.prepend(fqdnComponents.takeLast());
|
|
/// append remaining components of domain, becomes e.g. kde.org.mail
|
|
result.append(charDot).append(fqdnComponents.join(charDot));
|
|
/// append user name component of email address, becomes e.g. kde.org.mail.john.doe
|
|
result.append(charDot).append(emailComponents.first());
|
|
break;
|
|
}
|
|
case KGpgSettings::EnumEmailSorting::FQDNFirst:
|
|
{
|
|
/// get components of an email address
|
|
/// john.doe@mail.kde.org becomes [john.doe, mail.kde.org]
|
|
const QStringList emailComponents = result.split(charAt);
|
|
/// assemble result by joining components in reverse order,
|
|
/// separated by a dot, becomes e.g. mail.kde.org.john.doe
|
|
result = reverseListAndJoinWithChar(emailComponents, charDot);
|
|
break;
|
|
}
|
|
case KGpgSettings::EnumEmailSorting::Alphabetical:
|
|
/// do not modify email address except for lower-case conversion
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QVariant
|
|
KeyListProxyModelPrivate::dataSingleColumn(const QModelIndex &index, int role, const KGpgNode *node) const
|
|
{
|
|
Q_Q(const KeyListProxyModel);
|
|
|
|
if (index.column() != 0)
|
|
return QVariant();
|
|
|
|
switch (role) {
|
|
case Qt::DecorationRole:
|
|
if (node->getType() == ITYPE_UAT) {
|
|
if (m_previewsize > 0) {
|
|
const KGpgUatNode *nd = node->toUatNode();
|
|
return nd->getPixmap().scaled(m_previewsize + 5, m_previewsize, Qt::KeepAspectRatio);
|
|
} else {
|
|
return Images::photo();
|
|
}
|
|
} else {
|
|
return m_model->data(q->mapToSource(index), Qt::DecorationRole);
|
|
}
|
|
case Qt::DisplayRole: {
|
|
const QModelIndex srcidx(q->mapToSource(index));
|
|
const int srcrow = srcidx.row();
|
|
|
|
const QModelIndex ididx(srcidx.sibling(srcrow, KEYCOLUMN_ID));
|
|
const QString id(m_model->data(ididx, Qt::DisplayRole).toString().right(m_idLength));
|
|
|
|
const QModelIndex mailidx(srcidx.sibling(srcrow, KEYCOLUMN_EMAIL));
|
|
const QString mail(m_model->data(mailidx, Qt::DisplayRole).toString());
|
|
|
|
const QModelIndex nameidx(srcidx.sibling(srcrow, KEYCOLUMN_NAME));
|
|
const QString name(m_model->data(nameidx, Qt::DisplayRole).toString());
|
|
|
|
if (m_displaymode == KeyListProxyModel::SingleColumnIdFirst) {
|
|
if (mail.isEmpty())
|
|
return i18nc("ID: Name", "%1: %2", id, name);
|
|
else
|
|
return i18nc("ID: Name <Email>", "%1: %2 <%3>", id, name, mail);
|
|
} else {
|
|
if (mail.isEmpty())
|
|
return i18nc("Name: ID", "%1: %2", name, id);
|
|
else
|
|
return i18nc("Name <Email>: ID", "%1 <%2>: %3", name, mail, id);
|
|
}
|
|
}
|
|
case Qt::ToolTipRole: {
|
|
const QModelIndex srcidx(q->mapToSource(index));
|
|
const int srcrow = srcidx.row();
|
|
|
|
const QModelIndex ididx(srcidx.sibling(srcrow, KEYCOLUMN_ID));
|
|
return m_model->data(ididx, Qt::DisplayRole);
|
|
}
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
QVariant
|
|
KeyListProxyModelPrivate::dataMultiColumn(const QModelIndex &index, int role, const KGpgNode *node) const
|
|
{
|
|
Q_Q(const KeyListProxyModel);
|
|
|
|
if ((node->getType() == ITYPE_UAT) && (role == Qt::DecorationRole) && (index.column() == 0)) {
|
|
if (m_previewsize > 0) {
|
|
const KGpgUatNode *nd = node->toUatNode();
|
|
return nd->getPixmap().scaled(m_previewsize + 5, m_previewsize, Qt::KeepAspectRatio);
|
|
} else {
|
|
return Images::photo();
|
|
}
|
|
} else if ((role == Qt::DisplayRole) && (index.column() == KEYCOLUMN_ID)) {
|
|
QString id = m_model->data(q->mapToSource(index), Qt::DisplayRole).toString();
|
|
return id.right(m_idLength);
|
|
}
|
|
return m_model->data(q->mapToSource(index), role);
|
|
}
|
|
|
|
KeyListProxyModel::KeyListProxyModel(QObject *parent, const DisplayMode mode)
|
|
: QSortFilterProxyModel(parent),
|
|
d_ptr(new KeyListProxyModelPrivate(this, mode))
|
|
{
|
|
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
setFilterKeyColumn(-1);
|
|
setDynamicSortFilter(true);
|
|
}
|
|
|
|
KeyListProxyModel::~KeyListProxyModel()
|
|
{
|
|
delete d_ptr;
|
|
}
|
|
|
|
bool
|
|
KeyListProxyModel::hasChildren(const QModelIndex &idx) const
|
|
{
|
|
return sourceModel()->hasChildren(mapToSource(idx));
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::setKeyModel(KGpgItemModel *md)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
d->m_model = md;
|
|
setSourceModel(md);
|
|
}
|
|
|
|
bool
|
|
KeyListProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
|
{
|
|
Q_D(const KeyListProxyModel);
|
|
|
|
KGpgNode *l = d->m_model->nodeForIndex(left);
|
|
KGpgNode *r = d->m_model->nodeForIndex(right);
|
|
|
|
return d->lessThan(l, r, left.column());
|
|
}
|
|
|
|
bool
|
|
KeyListProxyModelPrivate::lessThan(const KGpgNode *left, const KGpgNode *right, const int column) const
|
|
{
|
|
const KGpgRootNode * const r = m_model->getRootNode();
|
|
Q_ASSERT(r != left);
|
|
Q_ASSERT(r != right);
|
|
|
|
if (r == left->getParentKeyNode()) {
|
|
if (r == right->getParentKeyNode()) {
|
|
if (left->getType() == ITYPE_GROUP) {
|
|
if (right->getType() == ITYPE_GROUP)
|
|
return left->getName() < right->getName();
|
|
else
|
|
return true;
|
|
} else if (right->getType() == ITYPE_GROUP)
|
|
return false;
|
|
|
|
// we don't need to care about group members here because they will never have root as parent
|
|
bool test1 = (left->getType() & ITYPE_PUBLIC) && !(left->getType() & ITYPE_SECRET); // only a public key
|
|
bool test2 = (right->getType() & ITYPE_PUBLIC) && !(right->getType() & ITYPE_SECRET); // only a public key
|
|
|
|
// key-pair goes before simple public key
|
|
// extra check needed to get sorting by trust right
|
|
if (left->getType() == ITYPE_PAIR && test2) return (column != KEYCOLUMN_TRUST);
|
|
if (right->getType() == ITYPE_PAIR && test1) return (column == KEYCOLUMN_TRUST);
|
|
|
|
return nodeLessThan(left, right, column);
|
|
} else {
|
|
return lessThan(left, right->getParentKeyNode(), column);
|
|
}
|
|
} else {
|
|
if (r == right->getParentKeyNode()) {
|
|
return lessThan(left->getParentKeyNode(), right, column);
|
|
} else if (left->getParentKeyNode() == right->getParentKeyNode()) {
|
|
if (left->getType() != right->getType())
|
|
return (left->getType() < right->getType());
|
|
|
|
return nodeLessThan(left, right, column);
|
|
} else {
|
|
return lessThan(left->getParentKeyNode(), right->getParentKeyNode(), column);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
KeyListProxyModelPrivate::nodeLessThan(const KGpgNode *left, const KGpgNode *right, const int column) const
|
|
{
|
|
Q_ASSERT(left->getType() == right->getType());
|
|
|
|
switch (column) {
|
|
case KEYCOLUMN_NAME:
|
|
if (left->getType() == ITYPE_SIGN) {
|
|
if (left->getName().startsWith(QLatin1Char( '[' )) && !right->getName().startsWith(QLatin1Char( '[' )))
|
|
return false;
|
|
else if (!left->getName().startsWith(QLatin1Char( '[' )) && right->getName().startsWith(QLatin1Char( '[' )))
|
|
return true;
|
|
else if (left->getName().startsWith(QLatin1Char( '[' )) && right->getName().startsWith(QLatin1Char( '[' )))
|
|
return (left->getId() < right->getId());
|
|
}
|
|
return (left->getName().compare(right->getName().toLower(), Qt::CaseInsensitive) < 0);
|
|
case KEYCOLUMN_EMAIL:
|
|
/// reverse email address to sort by TLD first, then domain, and account name last
|
|
return (reorderEmailComponents(left->getEmail()) < reorderEmailComponents(right->getEmail()));
|
|
case KEYCOLUMN_TRUST:
|
|
return (left->getTrust() < right->getTrust());
|
|
case KEYCOLUMN_EXPIR:
|
|
return (left->getExpiration() < right->getExpiration());
|
|
case KEYCOLUMN_SIZE:
|
|
if ((left->getType() & ITYPE_PAIR) && (right->getType() & ITYPE_PAIR)) {
|
|
unsigned int lsign, lenc, rsign, renc;
|
|
|
|
if (left->getType() & ITYPE_GROUP) {
|
|
const KGpgGroupMemberNode *g = static_cast<const KGpgGroupMemberNode *>(left);
|
|
|
|
lsign = g->getSignKeySize();
|
|
lenc = g->getEncryptionKeySize();
|
|
} else {
|
|
const KGpgKeyNode *g = static_cast<const KGpgKeyNode *>(left);
|
|
|
|
lsign = g->getSignKeySize();
|
|
lenc = g->getEncryptionKeySize();
|
|
}
|
|
|
|
if (right->getType() & ITYPE_GROUP) {
|
|
const KGpgGroupMemberNode *g = static_cast<const KGpgGroupMemberNode *>(right);
|
|
|
|
rsign = g->getSignKeySize();
|
|
renc = g->getEncryptionKeySize();
|
|
} else {
|
|
const KGpgKeyNode *g = static_cast<const KGpgKeyNode *>(right);
|
|
|
|
rsign = g->getSignKeySize();
|
|
renc = g->getEncryptionKeySize();
|
|
}
|
|
|
|
if (lsign != rsign)
|
|
return lsign < rsign;
|
|
else
|
|
return lenc < renc;
|
|
} else {
|
|
return (left->getSize() < right->getSize());
|
|
}
|
|
case KEYCOLUMN_CREAT:
|
|
return (left->getCreation() < right->getCreation());
|
|
default:
|
|
Q_ASSERT(column == KEYCOLUMN_ID);
|
|
return (left->getId().right(m_idLength) < right->getId().right(m_idLength));
|
|
}
|
|
}
|
|
|
|
bool
|
|
KeyListProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
|
{
|
|
Q_D(const KeyListProxyModel);
|
|
QModelIndex idx = d->m_model->index(source_row, 0, source_parent);
|
|
const KGpgNode *l = d->m_model->nodeForIndex(idx);
|
|
|
|
if (l == d->m_model->getRootNode())
|
|
return false;
|
|
|
|
if (d->m_onlysecret) {
|
|
switch (l->getType()) {
|
|
case ITYPE_PUBLIC:
|
|
case ITYPE_GPUBLIC:
|
|
case ITYPE_GROUP:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (d->m_displaymode) {
|
|
case SingleColumnIdFirst:
|
|
case SingleColumnIdLast:
|
|
if (l->getType() == ITYPE_GROUP)
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (l->getTrust() < d->m_mintrust)
|
|
return false;
|
|
|
|
/* check for expired signatures */
|
|
if ((d->m_mintrust > TRUST_EXPIRED) && (l->getType() == ITYPE_SIGN)) {
|
|
const QDateTime expDate = l->toSignNode()->getExpiration();
|
|
if (expDate.isValid() && (expDate < QDateTime::currentDateTime()))
|
|
return false;
|
|
}
|
|
|
|
if (l->getParentKeyNode() != d->m_model->getRootNode())
|
|
return true;
|
|
|
|
if (d->m_encryptionKeys && ((l->getType() & ITYPE_GROUP) == 0)) {
|
|
if (!l->toKeyNode()->canEncrypt())
|
|
return false;
|
|
}
|
|
|
|
if (l->getName().contains(filterRegExp()))
|
|
return true;
|
|
|
|
if (l->getEmail().contains(filterRegExp()))
|
|
return true;
|
|
|
|
if (l->getId().contains(filterRegExp()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::setOnlySecret(const bool b)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
d->m_onlysecret = b;
|
|
invalidateFilter();
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::settingsChanged()
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
const int newSort = KGpgSettings::emailSorting();
|
|
|
|
if (newSort != d->m_emailSorting) {
|
|
d->m_emailSorting = newSort;
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::setTrustFilter(const KgpgCore::KgpgKeyTrustFlag t)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
d->m_mintrust = t;
|
|
invalidateFilter();
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::setEncryptionKeyFilter(bool b)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
d->m_encryptionKeys = b;
|
|
invalidateFilter();
|
|
}
|
|
|
|
KGpgNode *
|
|
KeyListProxyModel::nodeForIndex(const QModelIndex &index) const
|
|
{
|
|
Q_D(const KeyListProxyModel);
|
|
|
|
return d->m_model->nodeForIndex(mapToSource(index));
|
|
}
|
|
|
|
QModelIndex
|
|
KeyListProxyModel::nodeIndex(KGpgNode *node)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
return mapFromSource(d->m_model->nodeIndex(node));
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::setPreviewSize(const int pixel)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
emit layoutAboutToBeChanged();
|
|
d->m_previewsize = pixel;
|
|
emit layoutChanged();
|
|
}
|
|
|
|
QVariant
|
|
KeyListProxyModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
Q_D(const KeyListProxyModel);
|
|
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
const KGpgNode *node = nodeForIndex(index);
|
|
|
|
switch (d->m_displaymode) {
|
|
case MultiColumn:
|
|
return d->dataMultiColumn(index, role, node);
|
|
case SingleColumnIdFirst:
|
|
case SingleColumnIdLast:
|
|
return d->dataSingleColumn(index, role, node);
|
|
}
|
|
|
|
Q_ASSERT(0);
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
KGpgItemModel *
|
|
KeyListProxyModel::getModel() const
|
|
{
|
|
Q_D(const KeyListProxyModel);
|
|
|
|
return d->m_model;
|
|
}
|
|
|
|
int
|
|
KeyListProxyModel::idLength() const
|
|
{
|
|
Q_D(const KeyListProxyModel);
|
|
|
|
return d->m_idLength;
|
|
}
|
|
|
|
void
|
|
KeyListProxyModel::setIdLength(const int length)
|
|
{
|
|
Q_D(KeyListProxyModel);
|
|
|
|
if (length == d->m_idLength)
|
|
return;
|
|
|
|
d->m_idLength = length;
|
|
invalidate();
|
|
}
|
|
|
|
bool
|
|
KeyListProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
Q_UNUSED(role);
|
|
|
|
if (value.type() != QVariant::String)
|
|
return false;
|
|
|
|
KGpgNode *node = nodeForIndex(index);
|
|
|
|
if (!node)
|
|
return false;
|
|
|
|
const QString newName = value.toString();
|
|
|
|
if (newName.isEmpty() || (newName == node->getName()))
|
|
return false;
|
|
|
|
node->toGroupNode()->rename(newName);
|
|
|
|
return true;
|
|
}
|
|
|
|
Qt::ItemFlags
|
|
KeyListProxyModel::flags(const QModelIndex &index) const
|
|
{
|
|
KGpgNode *node = nodeForIndex(index);
|
|
Qt::ItemFlags flags = QSortFilterProxyModel::flags(index);
|
|
|
|
if ((node->getType() == ITYPE_GROUP) && (index.column() == KEYCOLUMN_NAME))
|
|
flags |= Qt::ItemIsEditable;
|
|
|
|
return flags;
|
|
}
|
|
|
|
#include "keylistproxymodel.moc"
|