diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,6 +108,7 @@ ui/keyselectiondialog.cpp ui/keyrequester.cpp ui/keyapprovaldialog.cpp + ui/keyselectioncombo.cpp ) ki18n_wrap_ui(libkleo_ui_common_SRCS @@ -238,6 +239,7 @@ ProgressDialog KeyApprovalDialog KeySelectionDialog + KeySelectionCombo FileNameRequester KeyRequester MessageBox diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp --- a/src/models/keycache.cpp +++ b/src/models/keycache.cpp @@ -297,9 +297,9 @@ void KeyCache::Private::refreshJobDone(const KeyListResult &result) { - Q_EMIT q->keyListingDone(result); q->enableFileSystemWatcher(true); m_initalized = true; + Q_EMIT q->keyListingDone(result); } const Key &KeyCache::findByFingerprint(const char *fpr) const diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -17,3 +17,4 @@ add_kleo_test(test_keylister.cpp) add_kleo_test(test_auditlog.cpp) add_kleo_test(test_keyformailbox.cpp) +add_kleo_test(test_keyselectioncombo.cpp) diff --git a/src/tests/test_keyselectioncombo.cpp b/src/tests/test_keyselectioncombo.cpp new file mode 100644 --- /dev/null +++ b/src/tests/test_keyselectioncombo.cpp @@ -0,0 +1,93 @@ +/* + This file is part of libkleopatra's test suite. + Copyright (c) 2016 Klarälvdalens Datakonsult AB + + Libkleopatra is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License, + version 2, as published by the Free Software Foundation. + + Libkleopatra 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ui/keyselectioncombo.h" +#include "kleo/defaultkeyfilter.h" +#include + +#include +#include + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + KAboutData aboutData(QStringLiteral("test_keyselectioncombo"), i18n("KeySelectionCombo Test"), QStringLiteral("0.1")); + QCommandLineParser parser; + QCommandLineOption openpgpOption(QStringLiteral("openpgp"), i18n("Show OpenPGP keys")); + parser.addOption(openpgpOption); + QCommandLineOption smimeOption(QStringLiteral("smime"), i18n("Show S/MIME keys")); + parser.addOption(smimeOption); + QCommandLineOption encryptOption(QStringLiteral("encryption"), i18n("Show keys for encryption")); + parser.addOption(encryptOption); + QCommandLineOption signingOption(QStringLiteral("signing"), i18n("Show keys for signing")); + parser.addOption(signingOption); + + KAboutData::setApplicationData(aboutData); + parser.addVersionOption(); + parser.addHelpOption(); + aboutData.setupCommandLine(&parser); + parser.process(app); + aboutData.processCommandLine(&parser); + + QWidget window; + QVBoxLayout layout(&window); + + Kleo::KeySelectionCombo combo; + layout.addWidget(&combo); + + boost::shared_ptr filter(new Kleo::DefaultKeyFilter); + filter->setCanSign(parser.isSet(signingOption) ? Kleo::DefaultKeyFilter::Set : Kleo::DefaultKeyFilter::DoesNotMatter); + filter->setCanEncrypt(parser.isSet(encryptOption) ? Kleo::DefaultKeyFilter::Set : Kleo::DefaultKeyFilter::DoesNotMatter); + filter->setIsOpenPGP(parser.isSet(openpgpOption) ? Kleo::DefaultKeyFilter::Set : Kleo::DefaultKeyFilter::NotSet); + filter->setHasSecret(Kleo::DefaultKeyFilter::Set); + //filter->setOwnerTrust(Kleo::DefaultKeyFilter::IsAtLeast); + //filter->setOwnerTrustReferenceLevel(GpgME::Key::Ultimate); + combo.setKeyFilter(filter); + + combo.prependCustomItem(QIcon(), i18n("No key"), QStringLiteral("no-key")); + QObject::connect(&combo, &Kleo::KeySelectionCombo::currentKeyChanged, + [](const GpgME::Key &key) { + qDebug() << "Current key changed:" << key.keyID(); + }); + QObject::connect(&combo, &Kleo::KeySelectionCombo::customItemSelected, + [](const QVariant &data) { + qDebug() << "Custom item selected:" << data.toString(); + }); + + window.show(); + + /* + if (dlg.exec() == QDialog::Accepted) { + qDebug() << "accepted; selected key:" << (dlg.selectedKey().userID(0).id() ? dlg.selectedKey().userID(0).id() : "") << "\nselected _keys_:"; + for (std::vector::const_iterator it = dlg.selectedKeys().begin(); it != dlg.selectedKeys().end(); ++it) { + qDebug() << (it->userID(0).id() ? it->userID(0).id() : ""); + } + } else { + qDebug() << "rejected"; + } + */ + + return app.exec(); +} + diff --git a/src/ui/keyselectioncombo.h b/src/ui/keyselectioncombo.h new file mode 100644 --- /dev/null +++ b/src/ui/keyselectioncombo.h @@ -0,0 +1,66 @@ +/* This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2016 Klarälvdalens Datakonsult AB + + Kleopatra 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) any later version. + + Kleopatra 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef KLEO_KEYSELECTIONCOMBO_H +#define KLEO_KEYSELECTIONCOMBO_H + +#include + +#include + +#include +#include + +#include + +namespace GpgME +{ +class Key; +} + +namespace Kleo +{ +class KeyFilter; +class KeySelectionComboPrivate; + +class KLEO_EXPORT KeySelectionCombo : public QComboBox +{ + Q_OBJECT + +public: + explicit KeySelectionCombo(QWidget *parent = Q_NULLPTR); + virtual ~KeySelectionCombo(); + + void setKeyFilter(const boost::shared_ptr &kf); + + GpgME::Key currentKey() const; + void setCurrentKey(const GpgME::Key &key); + + void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data); + void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data); + +Q_SIGNALS: + void customItemSelected(const QVariant &data); + void currentKeyChanged(const GpgME::Key &key); + +private: + KeySelectionComboPrivate * const d; +}; + +} +#endif diff --git a/src/ui/keyselectioncombo.cpp b/src/ui/keyselectioncombo.cpp new file mode 100644 --- /dev/null +++ b/src/ui/keyselectioncombo.cpp @@ -0,0 +1,284 @@ +/* This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2016 Klarälvdalens Datakonsult AB + + Kleopatra 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) any later version. + + Kleopatra 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "keyselectioncombo.h" +#include + +#include "kleo/cryptobackendfactory.h" +#include "kleo/dn.h" +#include "models/keylistmodel.h" +#include "models/keylistsortfilterproxymodel.h" +#include "models/keycache.h" +#include "utils/formatting.h" +#include "progressbar.h" + +#include + +#include +#include + +#include +#include + +Q_DECLARE_METATYPE(GpgME::Key) + +namespace +{ + +class ProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +private: + struct CustomItem { + QIcon icon; + QString text; + QVariant data; + }; +public: + ProxyModel(QObject *parent = Q_NULLPTR) + : QSortFilterProxyModel(parent) + { + } + + ~ProxyModel() + { + qDeleteAll(mFrontItems); + qDeleteAll(mBackItems); + } + + bool isCustomItem(const int row) const + { + return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount(); + } + + void prependItem(const QIcon &icon, const QString &text, const QVariant &data) + { + beginInsertRows(QModelIndex(), 0, 0); + mFrontItems.push_front(new CustomItem{ icon, text, data }); + endInsertRows(); + } + + void appendItem(const QIcon &icon, const QString &text, const QVariant &data) + { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + mBackItems.push_back(new CustomItem{ icon, text, data }); + endInsertRows(); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE + { + return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count(); + } + + QModelIndex mapToSource(const QModelIndex &index) const Q_DECL_OVERRIDE + { + if (!isCustomItem(index.row())) { + const int row = index.row() - mFrontItems.count(); + const QModelIndex idx = createIndex(row, index.column(), index.internalPointer()); + return QSortFilterProxyModel::mapToSource(idx); + } else { + return QModelIndex(); + } + } + + QModelIndex mapFromSource(const QModelIndex &source_index) const Q_DECL_OVERRIDE + { + const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index); + return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer()); + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE + { + if (row < 0 || row >= rowCount()) { + return QModelIndex(); + } + if (row < mFrontItems.count()) { + return createIndex(row, column, mFrontItems[row]); + } else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) { + return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]); + } else { + const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent); + return createIndex(row, column, mi.internalPointer()); + } + } + + Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren; + } + + QModelIndex parent(const QModelIndex &) const Q_DECL_OVERRIDE + { + // Flat list + return QModelIndex(); + } + + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE + { + if (isCustomItem(index.row())) { + Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty()); + CustomItem *ci = static_cast(index.internalPointer()); + switch (role) { + case Qt::DisplayRole: + return ci->text; + case Qt::DecorationRole: + return ci->icon; + case Qt::UserRole: + return ci->data; + default: + return QVariant(); + } + } + + const auto key = QSortFilterProxyModel::data(index, Kleo::KeyListModelInterface::KeyRole).value(); + Q_ASSERT(!key.isNull()); + if (key.isNull()) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: { + const auto userID = key.userID(0); + QString name, email; + + if (key.protocol() == GpgME::OpenPGP) { + name = userID.name(); + email = userID.email(); + } else { + const Kleo::DN dn(userID.id()); + name = dn[QStringLiteral("CN")]; + email = dn[QStringLiteral("EMAIL")]; + } + return i18nc("Name (type, created: date, 0xkeyID)", "%1 (%2, created: %3, %4)", + email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name ", "%1 <%2>", name, email), + key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP") : i18n("S/MIME"), + Kleo::Formatting::creationDateString(key), + Kleo::Formatting::prettyKeyID(key.shortKeyID())); + } + case Qt::DecorationRole: + return Kleo::Formatting::iconForUid(key.userID(0)); + default: + return QSortFilterProxyModel::data(index, role); + } + + return QVariant(); + } + +private: + QVector mFrontItems; + QVector mBackItems; +}; + + +} // anonymous namespace + +namespace Kleo +{ +class KeySelectionComboPrivate +{ +public: + KeySelectionComboPrivate(KeySelectionCombo *parent) + : q(parent) + { + } + + Kleo::AbstractKeyListModel *model; + Kleo::KeyListSortFilterProxyModel *sortFilterProxy; + ProxyModel *proxyModel; + boost::shared_ptr cache; + +private: + KeySelectionCombo * const q; +}; + +} + +using namespace Kleo; + + +KeySelectionCombo::KeySelectionCombo(QWidget* parent) + : QComboBox(parent) + , d(new KeySelectionComboPrivate(this)) +{ + setEnabled(false); + + d->cache = Kleo::KeyCache::mutableInstance(); + connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, + this, [this]() { + qDebug() << "Key listing done"; + d->model->useKeyCache(true, true); + setEnabled(true); + setCurrentIndex(0); + }); + d->cache->startKeyListing(); + + d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this); + d->sortFilterProxy = new Kleo::KeyListSortFilterProxyModel(this); + d->sortFilterProxy->setSourceModel(d->model); + + d->proxyModel = new ProxyModel(this); + d->proxyModel->setSourceModel(d->sortFilterProxy); + + setModel(d->proxyModel); + connect(this, static_cast(&KeySelectionCombo::currentIndexChanged), + this, [this](int row) { + if (d->proxyModel->isCustomItem(row)) { + Q_EMIT customItemSelected(d->proxyModel->index(row, 0).data(Qt::UserRole)); + } else { + Q_EMIT currentKeyChanged(currentKey()); + } + }); +} + +KeySelectionCombo::~KeySelectionCombo() +{ + delete d; +} + +void KeySelectionCombo::setKeyFilter(const boost::shared_ptr &kf) +{ + d->sortFilterProxy->setKeyFilter(kf); +} + +GpgME::Key Kleo::KeySelectionCombo::currentKey() const +{ + return currentData(Kleo::KeyListModelInterface::KeyRole).value(); +} + +void Kleo::KeySelectionCombo::setCurrentKey(const GpgME::Key &key) +{ + const int idx = findData(QVariant::fromValue(key), Kleo::KeyListModelInterface::KeyRole, Qt::MatchExactly); + if (idx > -1) { + setCurrentIndex(idx); + } +} + +void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data) +{ + d->proxyModel->appendItem(icon, text, data); +} + +void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data) +{ + d->proxyModel->prependItem(icon, text, data); +} + + + +#include "keyselectioncombo.moc"