summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Vrátil <dvratil@kde.org>2016-08-02 14:36:26 (GMT)
committerDaniel Vrátil <dvratil@kde.org>2016-08-02 14:36:26 (GMT)
commit411863304ce47ea6fbb5a996eb215ac0fa7a42d1 (patch)
tree5e5b07f066107b507b86f0b3814992abe00f1ebd
parentafb221d52ba5d1fd01d40497cffd1b5d7a315cc0 (diff)
Import models and some utility classes from Kleopatra
-rw-r--r--CMakeLists.txt4
-rw-r--r--src/CMakeLists.txt48
-rw-r--r--src/models/keycache.cpp1095
-rw-r--r--src/models/keycache.h167
-rw-r--r--src/models/keycache_p.h71
-rw-r--r--src/models/keylistmodel.cpp1037
-rw-r--r--src/models/keylistmodel.h113
-rw-r--r--src/models/keylistmodelinterface.h88
-rw-r--r--src/models/keylistsortfilterproxymodel.cpp218
-rw-r--r--src/models/keylistsortfilterproxymodel.h100
-rw-r--r--src/models/keyrearrangecolumnsproxymodel.cpp81
-rw-r--r--src/models/keyrearrangecolumnsproxymodel.h60
-rw-r--r--src/models/modeltest.cpp530
-rw-r--r--src/models/modeltest.h75
-rw-r--r--src/models/subkeylistmodel.cpp221
-rw-r--r--src/models/subkeylistmodel.h96
-rw-r--r--src/models/useridlistmodel.cpp309
-rw-r--r--src/models/useridlistmodel.h79
-rw-r--r--src/utils/filesystemwatcher.cpp330
-rw-r--r--src/utils/filesystemwatcher.h85
-rw-r--r--src/utils/formatting.cpp791
-rw-r--r--src/utils/formatting.h138
22 files changed, 5730 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9dd686d..afa92e9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,7 +19,7 @@ include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
-set(PIM_VERSION "5.3.40")
+set(PIM_VERSION "5.3.41")
set(LIBKLEO_LIB_VERSION ${PIM_VERSION})
set(QT_REQUIRED_VERSION "5.5.0")
set(GPGMEPP_LIB_VERSION "5.3.40")
@@ -33,6 +33,8 @@ find_package(KF5WidgetsAddons ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5Completion ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5WindowSystem ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5CoreAddons ${KF5_VERSION} CONFIG REQUIRED)
+find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED)
+find_package(KF5ItemModels ${KF5_VERSION} CONFIG REQUIRED)
find_package(KF5Gpgmepp ${GPGMEPP_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5PimTextEdit ${KDEPIMTEXTEDIT_VERSION} CONFIG)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index af10603..d5df812 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -77,6 +77,15 @@ set(libkleo_core_SRCS
kleo/defaultkeyfilter.cpp
kleo/kconfigbasedkeyfilter.cpp
kleo/keyfiltermanager.cpp
+ models/keycache.cpp
+ models/keylistmodel.cpp
+ models/keylistsortfilterproxymodel.cpp
+ models/keyrearrangecolumnsproxymodel.cpp
+ models/modeltest.cpp
+ models/subkeylistmodel.cpp
+ models/useridlistmodel.cpp
+ utils/filesystemwatcher.cpp
+ utils/formatting.cpp
)
ecm_qt_declare_logging_category(libkleo_core_SRCS HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME log_libkleo)
@@ -116,7 +125,9 @@ set(kleo_LIB_LIBS PUBLIC KF5::QGpgme PRIVATE Qt5::Widgets
KF5::ConfigCore
KF5::CoreAddons
KF5::WindowSystem
- KF5::WidgetsAddons)
+ KF5::WidgetsAddons
+ KF5::Codecs
+ KF5::ItemModels)
if (WIN32)
set(kleo_LIB_LIBS ${kleo_LIB_LIBS} PUBLIC KF5::Gpgmepp)
else()
@@ -199,7 +210,30 @@ ecm_generate_headers(libkleo_CamelCase_HEADERS
RELATIVE kleo
)
-ecm_generate_headers(libkeo_Camelcaseui_HEADERS
+ecm_generate_headers(libkleo_CamelCase_models_HEADERS
+ HEADER_NAMES
+ KeyCache
+ KeyListModel
+ KeyListModelInterface
+ KeyListSortFilterProxyModel
+ KeyRearrangeColumnsProxyModel
+ SubkeyListModel
+ UserIDListModel
+ REQUIRED_HEADERS libkleo_models_HEADERS
+ PREFIX Libkleo
+ RELATIVE models
+)
+
+ecm_generate_headers(libkleo_CamelCase_utils_HEADERS
+ HEADER_NAMES
+ FileSystemWatcher
+ Formatting
+ REQUIRED_HEADERS libkleo_utils_HEADERS
+ PREFIX Libkleo
+ RELATIVE utils
+)
+
+ecm_generate_headers(libkleo_CamelCase_ui_HEADERS
HEADER_NAMES
ProgressDialog
KeyApprovalDialog
@@ -212,7 +246,7 @@ ecm_generate_headers(libkeo_Camelcaseui_HEADERS
CryptoConfigModule
DirectoryServicesWidget
DNAttributeOrderConfigWidget
- REQUIRED_HEADERS libkeo_ui_HEADERS
+ REQUIRED_HEADERS libkleo_ui_HEADERS
PREFIX Libkleo
RELATIVE ui
)
@@ -225,7 +259,9 @@ ecm_generate_pri_file(BASE_NAME Libkleo
install(FILES
${libkleo_CamelCase_HEADERS}
- ${libkeo_Camelcaseui_HEADERS}
+ ${libkleo_CamelCase_models_HEADERS}
+ ${libkleo_CamelCase_utils_HEADERS}
+ ${libkleo_CamelCase_ui_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo
COMPONENT Devel
)
@@ -234,7 +270,9 @@ install(FILES
${libkleo_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h
${libkleo_HEADERS}
- ${libkeo_ui_HEADERS}
+ ${libkleo_models_HEADERS}
+ ${libkleo_utils_HEADERS}
+ ${libkleo_ui_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/libkleo
COMPONENT Devel
)
diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp
new file mode 100644
index 0000000..5a01531
--- /dev/null
+++ b/src/models/keycache.cpp
@@ -0,0 +1,1095 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keycache.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007,2008 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "keycache.h"
+#include "keycache_p.h"
+
+#include "libkleo_debug.h"
+
+#include "kleo/predicates.h"
+#include "kleo/stl_util.h"
+#include "kleo/cryptobackendfactory.h"
+#include "kleo/dn.h"
+#include "kleo/keylistjob.h"
+#include "kleo/listallkeysjob.h"
+#include "utils/filesystemwatcher.h"
+
+#include <gpgme++/error.h>
+#include <gpgme++/key.h>
+#include <gpgme++/decryptionresult.h>
+#include <gpgme++/verificationresult.h>
+#include <gpgme++/keylistresult.h>
+
+#include <gpg-error.h>
+
+//#include <kmime/kmime_header_parsing.h>
+
+#include <KLocalizedString>
+
+#include <QPointer>
+#include <QTimer>
+#include <QEventLoop>
+
+#include <boost/bind.hpp>
+#include <boost/mem_fn.hpp>
+#include <boost/range.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/iterator/filter_iterator.hpp>
+
+#include <utility>
+#include <algorithm>
+#include <functional>
+#include <iterator>
+
+using namespace Kleo;
+using namespace GpgME;
+using namespace boost;
+using namespace KMime::Types;
+
+static const unsigned int hours2ms = 1000 * 60 * 60;
+
+//
+//
+// KeyCache
+//
+//
+
+namespace
+{
+
+make_comparator_str(ByEMail, .first.c_str());
+
+struct is_string_empty : std::unary_function<const char *, bool> {
+ bool operator()(const char *s) const
+ {
+ return !s || !*s;
+ }
+};
+
+}
+
+class KeyCache::Private
+{
+ friend class ::Kleo::KeyCache;
+ KeyCache *const q;
+public:
+ explicit Private(KeyCache *qq) : q(qq), m_refreshInterval(1), m_initalized(false)
+ {
+ connect(&m_autoKeyListingTimer, SIGNAL(timeout()), q, SLOT(startKeyListing()));
+ updateAutoKeyListingTimer();
+ }
+
+ template < template <template <typename U> class Op> class Comp>
+ std::vector<Key>::const_iterator find(const std::vector<Key> &keys, const char *key) const
+ {
+ ensureCachePopulated();
+ const std::vector<Key>::const_iterator it =
+ std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
+ if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
+ return it;
+ } else {
+ return keys.end();
+ }
+ }
+
+ template < template <template <typename U> class Op> class Comp>
+ std::vector<Subkey>::const_iterator find(const std::vector<Subkey> &keys, const char *key) const
+ {
+ ensureCachePopulated();
+ const std::vector<Subkey>::const_iterator it =
+ std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
+ if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
+ return it;
+ } else {
+ return keys.end();
+ }
+ }
+
+ std::vector<Key>::const_iterator find_fpr(const char *fpr) const
+ {
+ return find<_detail::ByFingerprint>(by.fpr, fpr);
+ }
+
+ std::pair< std::vector< std::pair<std::string, Key> >::const_iterator,
+ std::vector< std::pair<std::string, Key> >::const_iterator >
+ find_email(const char *email) const
+ {
+ ensureCachePopulated();
+ return std::equal_range(by.email.begin(), by.email.end(),
+ email, ByEMail<std::less>());
+ }
+
+ std::vector<Key> find_mailbox(const QString &email, bool sign) const;
+
+ std::vector<Subkey>::const_iterator find_subkeyid(const char *subkeyid) const
+ {
+ return find<_detail::ByKeyID>(by.subkeyid, subkeyid);
+ }
+
+ std::vector<Key>::const_iterator find_keyid(const char *keyid) const
+ {
+ return find<_detail::ByKeyID>(by.keyid, keyid);
+ }
+
+ std::vector<Key>::const_iterator find_shortkeyid(const char *shortkeyid) const
+ {
+ return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid);
+ }
+
+ std::pair <
+ std::vector<Key>::const_iterator,
+ std::vector<Key>::const_iterator
+ > find_subjects(const char *chain_id) const
+ {
+ ensureCachePopulated();
+ return std::equal_range(by.chainid.begin(), by.chainid.end(),
+ chain_id, _detail::ByChainID<std::less>());
+ }
+
+ void refreshJobDone(const KeyListResult &result);
+
+ void setRefreshInterval(int interval)
+ {
+ m_refreshInterval = interval;
+ updateAutoKeyListingTimer();
+ }
+
+ int refreshInterval() const
+ {
+ return m_refreshInterval;
+ }
+
+ void updateAutoKeyListingTimer()
+ {
+ setAutoKeyListingInterval(hours2ms * m_refreshInterval);
+ }
+ void setAutoKeyListingInterval(int ms)
+ {
+ m_autoKeyListingTimer.stop();
+ m_autoKeyListingTimer.setInterval(ms);
+ if (ms != 0) {
+ m_autoKeyListingTimer.start();
+ }
+ }
+
+ void ensureCachePopulated() const;
+
+private:
+ QPointer<RefreshKeysJob> m_refreshJob;
+ std::vector<shared_ptr<FileSystemWatcher> > m_fsWatchers;
+ QTimer m_autoKeyListingTimer;
+ int m_refreshInterval;
+
+ struct By {
+ std::vector<Key> fpr, keyid, shortkeyid, chainid;
+ std::vector< std::pair<std::string, Key> > email;
+ std::vector<Subkey> subkeyid;
+ } by;
+ bool m_initalized;
+};
+
+shared_ptr<const KeyCache> KeyCache::instance()
+{
+ return mutableInstance();
+}
+
+shared_ptr<KeyCache> KeyCache::mutableInstance()
+{
+ static weak_ptr<KeyCache> self;
+ try {
+ return shared_ptr<KeyCache>(self);
+ } catch (const bad_weak_ptr &) {
+ const shared_ptr<KeyCache> s(new KeyCache);
+ self = s;
+ return s;
+ }
+}
+
+KeyCache::KeyCache()
+ : QObject(), d(new Private(this))
+{
+
+}
+
+KeyCache::~KeyCache() {}
+
+void KeyCache::enableFileSystemWatcher(bool enable)
+{
+ Q_FOREACH (const shared_ptr<FileSystemWatcher> &i, d->m_fsWatchers) {
+ i->setEnabled(enable);
+ }
+}
+
+void KeyCache::setRefreshInterval(int hours)
+{
+ d->setRefreshInterval(hours);
+}
+
+int KeyCache::refreshInterval() const
+{
+ return d->refreshInterval();
+}
+
+void KeyCache::reload(GpgME::Protocol /*proto*/)
+{
+ if (d->m_refreshJob) {
+ return;
+ }
+
+ d->updateAutoKeyListingTimer();
+
+ enableFileSystemWatcher(false);
+ d->m_refreshJob = new RefreshKeysJob(this);
+ connect(d->m_refreshJob, SIGNAL(done(GpgME::KeyListResult)), this, SLOT(refreshJobDone(GpgME::KeyListResult)));
+ d->m_refreshJob->start();
+}
+
+void KeyCache::cancelKeyListing()
+{
+ if (!d->m_refreshJob) {
+ return;
+ }
+ d->m_refreshJob->cancel();
+}
+
+void KeyCache::addFileSystemWatcher(const shared_ptr<FileSystemWatcher> &watcher)
+{
+ if (!watcher) {
+ return;
+ }
+ d->m_fsWatchers.push_back(watcher);
+ connect(watcher.get(), SIGNAL(directoryChanged(QString)),
+ this, SLOT(startKeyListing()));
+ connect(watcher.get(), SIGNAL(fileChanged(QString)),
+ this, SLOT(startKeyListing()));
+
+ watcher->setEnabled(d->m_refreshJob == 0);
+}
+
+void KeyCache::Private::refreshJobDone(const KeyListResult &result)
+{
+ Q_EMIT q->keyListingDone(result);
+ q->enableFileSystemWatcher(true);
+ m_initalized = true;
+}
+
+const Key &KeyCache::findByFingerprint(const char *fpr) const
+{
+ const std::vector<Key>::const_iterator it = d->find_fpr(fpr);
+ if (it == d->by.fpr.end()) {
+ static const Key null;
+ return null;
+ } else {
+ return *it;
+ }
+}
+
+const Key &KeyCache::findByFingerprint(const std::string &fpr) const
+{
+ return findByFingerprint(fpr.c_str());
+}
+
+std::vector<Key> KeyCache::findByEMailAddress(const char *email) const
+{
+ const std::pair <
+ std::vector< std::pair<std::string, Key> >::const_iterator,
+ std::vector< std::pair<std::string, Key> >::const_iterator
+ > pair = d->find_email(email);
+ std::vector<Key> result;
+ result.reserve(std::distance(pair.first, pair.second));
+ std::transform(pair.first, pair.second,
+ std::back_inserter(result),
+ boost::bind(&std::pair<std::string, Key>::second, _1));
+ return result;
+}
+
+std::vector<Key> KeyCache::findByEMailAddress(const std::string &email) const
+{
+ return findByEMailAddress(email.c_str());
+}
+
+const Key &KeyCache::findByShortKeyID(const char *id) const
+{
+ const std::vector<Key>::const_iterator it = d->find_shortkeyid(id);
+ if (it != d->by.shortkeyid.end()) {
+ return *it;
+ }
+ static const Key null;
+ return null;
+}
+
+const Key &KeyCache::findByShortKeyID(const std::string &id) const
+{
+ return findByShortKeyID(id.c_str());
+}
+
+const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const
+{
+ {
+ // try by.fpr first:
+ const std::vector<Key>::const_iterator it = d->find_fpr(id);
+ if (it != d->by.fpr.end()) {
+ return *it;
+ }
+ }{
+ // try by.keyid next:
+ const std::vector<Key>::const_iterator it = d->find_keyid(id);
+ if (it != d->by.keyid.end()) {
+ return *it;
+ }
+ }
+ static const Key null;
+ return null;
+}
+
+const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const
+{
+ return findByKeyIDOrFingerprint(id.c_str());
+}
+
+std::vector<Key> KeyCache::findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const
+{
+
+ std::vector<std::string> keyids;
+ std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids),
+ boost::bind(is_string_empty(), boost::bind(&std::string::c_str, _1)));
+
+ // this is just case-insensitive string search:
+ std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint<std::less>());
+
+ std::vector<Key> result;
+ result.reserve(keyids.size()); // dups shouldn't happen
+ d->ensureCachePopulated();
+
+ kdtools::set_intersection(d->by.fpr.begin(), d->by.fpr.end(),
+ keyids.begin(), keyids.end(),
+ std::back_inserter(result),
+ _detail::ByFingerprint<std::less>());
+ if (result.size() < keyids.size()) {
+ // note that By{Fingerprint,KeyID,ShortKeyID} define the same
+ // order for _strings_
+ kdtools::set_intersection(d->by.keyid.begin(), d->by.keyid.end(),
+ keyids.begin(), keyids.end(),
+ std::back_inserter(result),
+ _detail::ByKeyID<std::less>());
+ }
+ // duplicates shouldn't happen, but make sure nonetheless:
+ std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
+ result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
+
+ // we skip looking into short key ids here, as it's highly
+ // unlikely they're used for this purpose. We might need to revise
+ // this decision, but only after testing.
+ return result;
+}
+
+std::vector<Subkey> KeyCache::findSubkeysByKeyID(const std::vector<std::string> &ids) const
+{
+ std::vector<std::string> sorted;
+ sorted.reserve(ids.size());
+ std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted),
+ boost::bind(is_string_empty(), boost::bind(&std::string::c_str, _1)));
+
+ std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
+
+ std::vector<Subkey> result;
+ d->ensureCachePopulated();
+ kdtools::set_intersection(d->by.subkeyid.begin(), d->by.subkeyid.end(),
+ sorted.begin(), sorted.end(),
+ std::back_inserter(result),
+ _detail::ByKeyID<std::less>());
+ return result;
+}
+
+std::vector<Key> KeyCache::findRecipients(const DecryptionResult &res) const
+{
+ std::vector<std::string> keyids;
+ Q_FOREACH (const DecryptionResult::Recipient &r, res.recipients())
+ if (const char *kid = r.keyID()) {
+ keyids.push_back(kid);
+ }
+ const std::vector<Subkey> subkeys = findSubkeysByKeyID(keyids);
+ std::vector<Key> result;
+ result.reserve(subkeys.size());
+ std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), boost::bind(&Subkey::parent, _1));
+
+ std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
+ result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
+ return result;
+}
+
+std::vector<Key> KeyCache::findSigners(const VerificationResult &res) const
+{
+ std::vector<std::string> fprs;
+ Q_FOREACH (const Signature &s, res.signatures())
+ if (const char *fpr = s.fingerprint()) {
+ fprs.push_back(fpr);
+ }
+ return findByKeyIDOrFingerprint(fprs);
+}
+
+std::vector<Key> KeyCache::findSigningKeysByMailbox(const QString &mb) const
+{
+ return d->find_mailbox(mb, true);
+}
+
+std::vector<Key> KeyCache::findEncryptionKeysByMailbox(const QString &mb) const
+{
+ return d->find_mailbox(mb, false);
+}
+
+namespace
+{
+#define DO( op, meth, meth2 ) if ( op key.meth() ) {} else { qDebug( "rejecting for signing: %s: %s", #meth2, key.primaryFingerprint() ); return false; }
+#define ACCEPT( meth ) DO( !!, meth, !meth )
+#define REJECT( meth ) DO( !, meth, meth )
+struct ready_for_signing : std::unary_function<Key, bool> {
+ bool operator()(const Key &key) const
+ {
+#if 1
+ ACCEPT(hasSecret);
+ ACCEPT(canReallySign);
+ REJECT(isRevoked);
+ REJECT(isExpired);
+ REJECT(isDisabled);
+ REJECT(isInvalid);
+ return true;
+#else
+ return key.hasSecret() &&
+ key.canReallySign() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
+#endif
+#undef DO
+ }
+};
+
+struct ready_for_encryption : std::unary_function<Key, bool> {
+#define DO( op, meth, meth2 ) if ( op key.meth() ) {} else { qDebug( "rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint() ); return false; }
+ bool operator()(const Key &key) const
+ {
+#if 1
+ ACCEPT(canEncrypt);
+ REJECT(isRevoked);
+ REJECT(isExpired);
+ REJECT(isDisabled);
+ REJECT(isInvalid);
+ return true;
+#else
+ return
+ key.canEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
+#endif
+ }
+#undef DO
+#undef ACCEPT
+#undef REJECT
+};
+}
+
+std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
+{
+ if (email.isEmpty()) {
+ return std::vector<Key>();
+ }
+
+ const std::pair <
+ std::vector< std::pair<std::string, Key> >::const_iterator,
+ std::vector< std::pair<std::string, Key> >::const_iterator
+ > pair = find_email(email.toUtf8().constData());
+
+ std::vector<Key> result;
+ result.reserve(std::distance(pair.first, pair.second));
+ if (sign)
+ kdtools::copy_2nd_if(pair.first, pair.second,
+ std::back_inserter(result),
+ ready_for_signing());
+ else
+ kdtools::copy_2nd_if(pair.first, pair.second,
+ std::back_inserter(result),
+ ready_for_encryption());
+ return result;
+}
+
+std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
+{
+ return findSubjects(std::vector<Key>(1, key), options);
+}
+
+std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
+{
+ return findSubjects(keys.begin(), keys.end(), options);
+}
+
+std::vector<Key> KeyCache::findSubjects(std::vector<Key>::const_iterator first, std::vector<Key>::const_iterator last, Options options) const
+{
+
+ if (first == last) {
+ return std::vector<Key>();
+ }
+
+ std::vector<Key> result;
+ while (first != last) {
+ const std::pair <
+ std::vector<Key>::const_iterator,
+ std::vector<Key>::const_iterator
+ > pair = d->find_subjects(first->primaryFingerprint());
+ result.insert(result.end(), pair.first, pair.second);
+ ++first;
+ }
+
+ std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
+ result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
+
+ if (options & RecursiveSearch) {
+ const std::vector<Key> furtherSubjects = findSubjects(result, options);
+ std::vector<Key> combined;
+ combined.reserve(result.size() + furtherSubjects.size());
+ std::merge(result.begin(), result.end(),
+ furtherSubjects.begin(), furtherSubjects.end(),
+ std::back_inserter(combined),
+ _detail::ByFingerprint<std::less>());
+ combined.erase(std::unique(combined.begin(), combined.end(), _detail::ByFingerprint<std::equal_to>()), combined.end());
+ result.swap(combined);
+ }
+
+ return result;
+}
+
+std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
+{
+
+ if (key.isNull()) {
+ return std::vector<Key>();
+ }
+
+ std::vector<Key> result;
+ if (options & IncludeSubject) {
+ result.push_back(key);
+ }
+
+ if (key.isRoot()) {
+ return result;
+ }
+
+ const Key &issuer = findByFingerprint(key.chainID());
+
+ if (issuer.isNull()) {
+ return result;
+ }
+
+ result.push_back(issuer);
+
+ if (!(options & RecursiveSearch)) {
+ return result;
+ }
+
+ while (!result.back().isNull() && !result.back().isRoot()) {
+ result.push_back(findByFingerprint(result.back().chainID()));
+ }
+
+ if (result.back().isNull()) {
+ result.pop_back();
+ }
+
+ return result;
+}
+
+std::vector<Key> KeyCache::findIssuers(const std::vector<Key> &keys, Options options) const
+{
+ return findIssuers(keys.begin(), keys.end(), options);
+}
+
+std::vector<Key> KeyCache::findIssuers(std::vector<Key>::const_iterator first, std::vector<Key>::const_iterator last, Options options) const
+{
+
+ if (first == last) {
+ return std::vector<Key>();
+ }
+
+ // extract chain-ids, identifying issuers:
+ std::vector<const char *> chainIDs;
+ chainIDs.reserve(last - first);
+ std::transform(boost::make_filter_iterator(!boost::bind(&Key::isRoot, _1), first, last),
+ boost::make_filter_iterator(!boost::bind(&Key::isRoot, _1), last, last),
+ std::back_inserter(chainIDs),
+ boost::bind(&Key::chainID, _1));
+ std::sort(chainIDs.begin(), chainIDs.end(), _detail::ByFingerprint<std::less>());
+
+ const std::vector<const char *>::iterator lastUniqueChainID = std::unique(chainIDs.begin(), chainIDs.end(), _detail::ByFingerprint<std::less>());
+
+ std::vector<Key> result;
+ result.reserve(lastUniqueChainID - chainIDs.begin());
+
+ d->ensureCachePopulated();
+
+ kdtools::set_intersection(d->by.fpr.begin(), d->by.fpr.end(),
+ chainIDs.begin(), lastUniqueChainID,
+ std::back_inserter(result),
+ _detail::ByFingerprint<std::less>());
+
+ if (options & IncludeSubject) {
+ const unsigned int rs = result.size();
+ result.insert(result.end(), first, last);
+ std::inplace_merge(result.begin(), result.begin() + rs, result.end(),
+ _detail::ByFingerprint<std::less>());
+ }
+
+ if (!(options & RecursiveSearch)) {
+ return result;
+ }
+
+ const std::vector<Key> l2result = findIssuers(result, options & ~IncludeSubject);
+
+ const unsigned long result_size = result.size();
+ result.insert(result.end(), l2result.begin(), l2result.end());
+ std::inplace_merge(result.begin(), result.begin() + result_size, result.end(),
+ _detail::ByFingerprint<std::less>());
+ return result;
+}
+
+static std::string email(const UserID &uid)
+{
+ const std::string email = uid.email();
+ if (email.empty()) {
+ return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
+ }
+ if (email[0] == '<' && email[email.size() - 1] == '>') {
+ return email.substr(1, email.size() - 2);
+ } else {
+ return email;
+ }
+}
+
+static std::vector<std::string> emails(const Key &key)
+{
+ std::vector<std::string> emails;
+ Q_FOREACH (const UserID &uid, key.userIDs()) {
+ const std::string e = email(uid);
+ if (!e.empty()) {
+ emails.push_back(e);
+ }
+ }
+ std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
+ emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
+ return emails;
+}
+
+void KeyCache::remove(const Key &key)
+{
+ if (key.isNull()) {
+ return;
+ }
+
+ const char *fpr = key.primaryFingerprint();
+ if (!fpr) {
+ return;
+ }
+
+ Q_EMIT aboutToRemove(key);
+
+ {
+ const std::pair<std::vector<Key>::iterator, std::vector<Key>::iterator> range
+ = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr,
+ _detail::ByFingerprint<std::less>());
+ d->by.fpr.erase(range.first, range.second);
+ }
+
+ if (const char *keyid = key.keyID()) {
+ const std::pair<std::vector<Key>::iterator, std::vector<Key>::iterator> range
+ = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid,
+ _detail::ByKeyID<std::less>());
+ const std::vector<Key>::iterator it
+ = std::remove_if(begin(range), end(range), boost::bind(_detail::ByFingerprint<std::equal_to>(), fpr, _1));
+ d->by.keyid.erase(it, end(range));
+ }
+
+ if (const char *shortkeyid = key.shortKeyID()) {
+ const std::pair<std::vector<Key>::iterator, std::vector<Key>::iterator> range
+ = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid,
+ _detail::ByShortKeyID<std::less>());
+ const std::vector<Key>::iterator it
+ = std::remove_if(begin(range), end(range), boost::bind(_detail::ByFingerprint<std::equal_to>(), fpr, _1));
+ d->by.shortkeyid.erase(it, end(range));
+ }
+
+ if (const char *chainid = key.chainID()) {
+ const std::pair<std::vector<Key>::iterator, std::vector<Key>::iterator> range
+ = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid,
+ _detail::ByChainID<std::less>());
+ const std::pair< std::vector<Key>::iterator, std::vector<Key>::iterator > range2
+ = std::equal_range(begin(range), end(range), fpr, _detail::ByFingerprint<std::less>());
+ d->by.chainid.erase(begin(range2), end(range2));
+ }
+
+ Q_FOREACH (const std::string &email, emails(key)) {
+ const std::pair<std::vector<std::pair<std::string, Key> >::iterator, std::vector<std::pair<std::string, Key> >::iterator> range
+ = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
+ const std::vector< std::pair<std::string, Key> >::iterator it
+ = std::remove_if(begin(range), end(range), boost::bind(qstricmp, fpr, boost::bind(&Key::primaryFingerprint, boost::bind(&std::pair<std::string, Key>::second, _1))) == 0);
+ d->by.email.erase(it, end(range));
+ }
+
+ Q_FOREACH (const Subkey &subkey, key.subkeys()) {
+ if (const char *keyid = subkey.keyID()) {
+ const std::pair<std::vector<Subkey>::iterator, std::vector<Subkey>::iterator> range
+ = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid,
+ _detail::ByKeyID<std::less>());
+ const std::pair< std::vector<Subkey>::iterator, std::vector<Subkey>::iterator > range2
+ = std::equal_range(begin(range), end(range), fpr, _detail::ByKeyID<std::less>());
+ d->by.subkeyid.erase(begin(range2), end(range2));
+ }
+ }
+}
+
+void KeyCache::remove(const std::vector<Key> &keys)
+{
+ Q_FOREACH (const Key &key, keys) {
+ remove(key);
+ }
+}
+
+const std::vector<GpgME::Key> &KeyCache::keys() const
+{
+ d->ensureCachePopulated();
+ return d->by.fpr;
+}
+
+std::vector<Key> KeyCache::secretKeys() const
+{
+ std::vector<Key> keys = this->keys();
+ keys.erase(std::remove_if(keys.begin(), keys.end(), !boost::bind(&Key::hasSecret, _1)), keys.end());
+ return keys;
+}
+
+void KeyCache::refresh(const std::vector<Key> &keys)
+{
+ // make this better...
+ clear();
+ insert(keys);
+}
+
+void KeyCache::insert(const Key &key)
+{
+ insert(std::vector<Key>(1, key));
+}
+
+namespace
+{
+
+template <
+ template <template <typename T> class Op> class T1,
+ template <template <typename T> class Op> class T2
+ > struct lexicographically
+{
+ typedef bool result_type;
+
+ template <typename U, typename V>
+ bool operator()(const U &lhs, const V &rhs) const
+ {
+ return
+ T1<std::less>()(lhs, rhs) ||
+ (T1<std::equal_to>()(lhs, rhs) &&
+ T2<std::less>()(lhs, rhs))
+ ;
+ }
+};
+
+}
+
+void KeyCache::insert(const std::vector<Key> &keys)
+{
+
+ // 1. remove those with empty fingerprints:
+ std::vector<Key> sorted;
+ sorted.reserve(keys.size());
+ std::remove_copy_if(keys.begin(), keys.end(),
+ std::back_inserter(sorted),
+ boost::bind(is_string_empty(), boost::bind(&Key::primaryFingerprint, _1)));
+
+ Q_FOREACH (const Key &key, sorted) {
+ remove(key); // this is sub-optimal, but makes implementation from here on much easier
+ }
+
+ // 2. sort by fingerprint:
+ std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
+
+ // 2a. insert into fpr index:
+ std::vector<Key> by_fpr;
+ by_fpr.reserve(sorted.size() + d->by.fpr.size());
+ std::merge(sorted.begin(), sorted.end(),
+ d->by.fpr.begin(), d->by.fpr.end(),
+ std::back_inserter(by_fpr),
+ _detail::ByFingerprint<std::less>());
+
+ // 3. build email index:
+ std::vector< std::pair<std::string, Key> > pairs;
+ pairs.reserve(sorted.size());
+ Q_FOREACH (const Key &key, sorted) {
+ const std::vector<std::string> emails = ::emails(key);
+ Q_FOREACH (const std::string &e, emails) {
+ pairs.push_back(std::make_pair(e, key));
+ }
+ }
+ std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
+
+ // 3a. insert into email index:
+ std::vector< std::pair<std::string, Key> > by_email;
+ by_email.reserve(pairs.size() + d->by.email.size());
+ std::merge(pairs.begin(), pairs.end(),
+ d->by.email.begin(), d->by.email.end(),
+ std::back_inserter(by_email),
+ ByEMail<std::less>());
+
+ // 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
+ std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
+
+ // 3.5a: insert into chain-id index:
+ std::vector<Key> by_chainid;
+ by_chainid.reserve(sorted.size() + d->by.chainid.size());
+ std::merge(boost::make_filter_iterator(!boost::bind(&Key::isRoot, _1), sorted.begin(), sorted.end()),
+ boost::make_filter_iterator(!boost::bind(&Key::isRoot, _1), sorted.end(), sorted.end()),
+ d->by.chainid.begin(), d->by.chainid.end(),
+ std::back_inserter(by_chainid),
+ lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
+
+ // 4. sort by key id:
+ std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
+
+ // 4a. insert into keyid index:
+ std::vector<Key> by_keyid;
+ by_keyid.reserve(sorted.size() + d->by.keyid.size());
+ std::merge(sorted.begin(), sorted.end(),
+ d->by.keyid.begin(), d->by.keyid.end(),
+ std::back_inserter(by_keyid),
+ _detail::ByKeyID<std::less>());
+
+ // 5. sort by short key id:
+ std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID<std::less>());
+
+ // 5a. insert into short keyid index:
+ std::vector<Key> by_shortkeyid;
+ by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size());
+ std::merge(sorted.begin(), sorted.end(),
+ d->by.shortkeyid.begin(), d->by.shortkeyid.end(),
+ std::back_inserter(by_shortkeyid),
+ _detail::ByShortKeyID<std::less>());
+
+ // 6. build subkey ID index:
+ std::vector<Subkey> subkeys;
+ subkeys.reserve(sorted.size());
+ Q_FOREACH (const Key &key, sorted)
+ Q_FOREACH (const Subkey &subkey, key.subkeys()) {
+ subkeys.push_back(subkey);
+ }
+
+ // 6a sort by key id:
+ std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
+
+ // 6b. insert into subkey ID index:
+ std::vector<Subkey> by_subkeyid;
+ by_email.reserve(subkeys.size() + d->by.subkeyid.size());
+ std::merge(subkeys.begin(), subkeys.end(),
+ d->by.subkeyid.begin(), d->by.subkeyid.end(),
+ std::back_inserter(by_subkeyid),
+ _detail::ByKeyID<std::less>());
+
+ // now commit (well, we already removed keys...)
+ by_fpr.swap(d->by.fpr);
+ by_keyid.swap(d->by.keyid);
+ by_shortkeyid.swap(d->by.shortkeyid);
+ by_email.swap(d->by.email);
+ by_subkeyid.swap(d->by.subkeyid);
+ by_chainid.swap(d->by.chainid);
+
+ Q_FOREACH (const Key &key, sorted) {
+ Q_EMIT added(key);
+ }
+
+ Q_EMIT keysMayHaveChanged();
+}
+
+void KeyCache::clear()
+{
+ d->by = Private::By();
+}
+
+//
+//
+// RefreshKeysJob
+//
+//
+
+class KeyCache::RefreshKeysJob::Private
+{
+ RefreshKeysJob *const q;
+public:
+ Private(KeyCache *cache, RefreshKeysJob *qq);
+ void doStart();
+ Error startKeyListing(const char *protocol);
+ void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
+ {
+ std::vector<Key> keys;
+ keys.reserve(m_keys.size() + nextKeys.size());
+ if (m_keys.empty()) {
+ keys = nextKeys;
+ } else
+ std::merge(m_keys.begin(), m_keys.end(),
+ nextKeys.begin(), nextKeys.end(),
+ std::back_inserter(keys),
+ _detail::ByFingerprint<std::less>());
+ m_keys.swap(keys);
+ jobDone(res);
+ }
+ void emitDone(const KeyListResult &result);
+ void updateKeyCache();
+
+ KeyCache *m_cache;
+ uint m_jobsPending;
+ std::vector<Key> m_keys;
+ KeyListResult m_mergedResult;
+
+private:
+ void jobDone(const KeyListResult &res);
+};
+
+KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq) : q(qq), m_cache(cache), m_jobsPending(0)
+{
+ assert(m_cache);
+}
+
+void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
+{
+ QObject *const sender = q->sender();
+ if (sender) {
+ sender->disconnect(q);
+ }
+ assert(m_jobsPending > 0);
+ --m_jobsPending;
+ m_mergedResult.mergeWith(result);
+ if (m_jobsPending > 0) {
+ return;
+ }
+ updateKeyCache();
+ emitDone(m_mergedResult);
+}
+
+void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
+{
+ q->deleteLater();
+ Q_EMIT q->done(res);
+}
+
+KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent) : QObject(parent), d(new Private(cache, this))
+{
+}
+
+KeyCache::RefreshKeysJob::~RefreshKeysJob() {}
+
+void KeyCache::RefreshKeysJob::start()
+{
+ QTimer::singleShot(0, this, SLOT(doStart()));
+}
+
+void KeyCache::RefreshKeysJob::cancel()
+{
+ Q_EMIT canceled();
+}
+
+void KeyCache::RefreshKeysJob::Private::doStart()
+{
+ assert(m_jobsPending == 0);
+ m_mergedResult.mergeWith(KeyListResult(startKeyListing("openpgp")));
+ m_mergedResult.mergeWith(KeyListResult(startKeyListing("smime")));
+
+ if (m_jobsPending != 0) {
+ return;
+ }
+
+ const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
+ emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
+}
+
+void KeyCache::RefreshKeysJob::Private::updateKeyCache()
+{
+ std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
+ std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
+ std::vector<Key> keysToRemove;
+ std::set_difference(cachedKeys.begin(), cachedKeys.end(),
+ m_keys.begin(), m_keys.end(),
+ std::back_inserter(keysToRemove),
+ _detail::ByFingerprint<std::less>());
+ m_cache->remove(keysToRemove);
+ m_cache->refresh(m_keys);
+}
+
+Error KeyCache::RefreshKeysJob::Private::startKeyListing(const char *backend)
+{
+ const Kleo::CryptoBackend::Protocol *const protocol = Kleo::CryptoBackendFactory::instance()->protocol(backend);
+ if (!protocol) {
+ return Error();
+ }
+ Kleo::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/false, /*validate*/true);
+ if (!job) {
+ return Error();
+ }
+ connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector<GpgME::Key>)),
+ q, SLOT(listAllKeysJobDone(GpgME::KeyListResult,std::vector<GpgME::Key>)));
+
+ connect(q, &RefreshKeysJob::canceled,
+ job, &Job::slotCancel);
+
+ const Error error = job->start(true);
+
+ if (!error && !error.isCanceled()) {
+ ++m_jobsPending;
+ }
+ return error;
+}
+
+bool KeyCache::initialized() const
+{
+ return d->m_initalized;
+}
+
+void KeyCache::Private::ensureCachePopulated() const
+{
+ if (!m_initalized) {
+ QEventLoop loop;
+ loop.connect(q, &KeyCache::keyListingDone,
+ &loop, &QEventLoop::quit);
+ qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
+ loop.exec();
+ qCDebug(LIBKLEO_LOG) << "Keycache available.";
+ }
+}
+
+#include "moc_keycache_p.cpp"
+#include "moc_keycache.cpp"
+
diff --git a/src/models/keycache.h b/src/models/keycache.h
new file mode 100644
index 0000000..a181995
--- /dev/null
+++ b/src/models/keycache.h
@@ -0,0 +1,167 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keycache.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KLEOPATRA_MODELS_KEYCACHE_H__
+#define __KLEOPATRA_MODELS_KEYCACHE_H__
+
+#include <QObject>
+
+#include <kleo_export.h>
+
+#include <gpgme++/global.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+namespace GpgME
+{
+class Key;
+class DecryptionResult;
+class VerificationResult;
+class KeyListResult;
+class Subkey;
+}
+
+namespace KMime
+{
+namespace Types
+{
+class Mailbox;
+}
+}
+
+namespace Kleo
+{
+
+class FileSystemWatcher;
+
+class KLEO_EXPORT KeyCache : public QObject
+{
+ Q_OBJECT
+protected:
+ explicit KeyCache();
+public:
+ static boost::shared_ptr<const KeyCache> instance();
+ static boost::shared_ptr<KeyCache> mutableInstance();
+
+ ~KeyCache();
+
+ void insert(const GpgME::Key &key);
+ void insert(const std::vector<GpgME::Key> &keys);
+
+ void refresh(const std::vector<GpgME::Key> &keys);
+
+ void remove(const GpgME::Key &key);
+ void remove(const std::vector<GpgME::Key> &keys);
+
+ void addFileSystemWatcher(const boost::shared_ptr<FileSystemWatcher> &watcher);
+
+ void enableFileSystemWatcher(bool enable);
+
+ void setRefreshInterval(int hours);
+ int refreshInterval() const;
+
+ const std::vector<GpgME::Key> &keys() const;
+ std::vector<GpgME::Key> secretKeys() const;
+
+ const GpgME::Key &findByFingerprint(const char *fpr) const;
+ const GpgME::Key &findByFingerprint(const std::string &fpr) const;
+
+ std::vector<GpgME::Key> findByEMailAddress(const char *email) const;
+ std::vector<GpgME::Key> findByEMailAddress(const std::string &email) const;
+
+ const GpgME::Key &findByShortKeyID(const char *id) const;
+ const GpgME::Key &findByShortKeyID(const std::string &id) const;
+
+ const GpgME::Key &findByKeyIDOrFingerprint(const char *id) const;
+ const GpgME::Key &findByKeyIDOrFingerprint(const std::string &id) const;
+
+ std::vector<GpgME::Key> findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const;
+
+ std::vector<GpgME::Subkey> findSubkeysByKeyID(const std::vector<std::string> &ids) const;
+
+ std::vector<GpgME::Key> findRecipients(const GpgME::DecryptionResult &result) const;
+ std::vector<GpgME::Key> findSigners(const GpgME::VerificationResult &result) const;
+
+ std::vector<GpgME::Key> findSigningKeysByMailbox(const QString &mb) const;
+ std::vector<GpgME::Key> findEncryptionKeysByMailbox(const QString &mb) const;
+
+ enum Option {
+ NoOption = 0,
+ RecursiveSearch = 1,
+ IncludeSubject = 2
+ };
+ Q_DECLARE_FLAGS(Options, Option)
+
+ std::vector<GpgME::Key> findSubjects(const GpgME::Key &key, Options option = RecursiveSearch) const;
+ std::vector<GpgME::Key> findSubjects(const std::vector<GpgME::Key> &keys, Options options = RecursiveSearch) const;
+ std::vector<GpgME::Key> findSubjects(std::vector<GpgME::Key>::const_iterator first, std::vector<GpgME::Key>::const_iterator last, Options options = RecursiveSearch) const;
+
+ std::vector<GpgME::Key> findIssuers(const GpgME::Key &key, Options options = RecursiveSearch) const;
+ std::vector<GpgME::Key> findIssuers(const std::vector<GpgME::Key> &keys, Options options = RecursiveSearch) const;
+ std::vector<GpgME::Key> findIssuers(std::vector<GpgME::Key>::const_iterator first, std::vector<GpgME::Key>::const_iterator last, Options options = RecursiveSearch) const;
+
+ /** Check if at least one keylisting was finished. */
+ bool initialized() const;
+
+public Q_SLOTS:
+ void clear();
+ void startKeyListing(GpgME::Protocol proto = GpgME::UnknownProtocol)
+ {
+ reload(proto);
+ }
+ void reload(GpgME::Protocol proto = GpgME::UnknownProtocol);
+ void cancelKeyListing();
+
+Q_SIGNALS:
+ //void changed( const GpgME::Key & key );
+ void aboutToRemove(const GpgME::Key &key);
+ void added(const GpgME::Key &key);
+ void keyListingDone(const GpgME::KeyListResult &result);
+ void keysMayHaveChanged();
+
+private:
+ class RefreshKeysJob;
+
+ class Private;
+ QScopedPointer<Private> const d;
+
+ Q_PRIVATE_SLOT(d, void refreshJobDone(GpgME::KeyListResult))
+};
+
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::KeyCache::Options)
+
+#endif /* __KLEOPATRA_MODELS_KEYCACHE_H__ */
diff --git a/src/models/keycache_p.h b/src/models/keycache_p.h
new file mode 100644
index 0000000..f17a792
--- /dev/null
+++ b/src/models/keycache_p.h
@@ -0,0 +1,71 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keycache_p.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2008 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KLEOPATRA_KEYCACHE_P_H__
+#define __KLEOPATRA_KEYCACHE_P_H__
+
+#include "keycache.h"
+
+namespace GpgME
+{
+class KeyListResult;
+}
+
+namespace Kleo
+{
+
+class KeyCache::RefreshKeysJob : public QObject
+{
+ Q_OBJECT
+public:
+
+ explicit RefreshKeysJob(KeyCache *cache, QObject *parent = Q_NULLPTR);
+ ~RefreshKeysJob();
+
+ void start();
+ void cancel();
+
+Q_SIGNALS:
+ void done(const GpgME::KeyListResult &);
+ void canceled();
+
+private:
+ class Private;
+ friend class Private;
+ Private * const d;
+
+ Q_PRIVATE_SLOT(d, void listAllKeysJobDone(GpgME::KeyListResult, std::vector<GpgME::Key>))
+ Q_PRIVATE_SLOT(d, void doStart())
+};
+}
+
+#endif // __KLEOPATRA_KEYCACHE_P_H__
diff --git a/src/models/keylistmodel.cpp b/src/models/keylistmodel.cpp
new file mode 100644
index 0000000..b0afa8d
--- /dev/null
+++ b/src/models/keylistmodel.cpp
@@ -0,0 +1,1037 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keylistmodel.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "keylistmodel.h"
+#include "keycache.h"
+#include "kleo/predicates.h"
+#include "kleo/keyfiltermanager.h"
+#include "kleo/keyfilter.h"
+#include "utils/formatting.h"
+
+#ifdef KLEO_MODEL_TEST
+# include "modeltest.h"
+#endif
+
+#include <Libkleo/KeyFilterManager>
+#include <Libkleo/KeyFilter>
+
+#include <KLocalizedString>
+
+#include <QFont>
+#include <QColor>
+#include <QHash>
+#include <QIcon>
+#include <QDate>
+#include <gpgme++/key.h>
+
+#ifndef Q_MOC_RUN // QTBUG-22829
+#include <boost/bind.hpp>
+#include <boost/graph/topological_sort.hpp>
+#include <boost/graph/adjacency_list.hpp>
+#endif
+
+#include <algorithm>
+#include <vector>
+#include <map>
+#include <set>
+#include <iterator>
+#include <cassert>
+
+#ifdef __GLIBCXX__
+#include <ext/algorithm> // for is_sorted
+#endif
+
+using namespace GpgME;
+using namespace Kleo;
+
+Q_DECLARE_METATYPE(Key);
+
+/****************************************************************************
+**
+** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information
+** to ensure GNU General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html. In addition, as a special
+** exception, Nokia gives you certain additional rights. These rights
+** are described in the Nokia Qt GPL Exception version 1.3, included in
+** the file GPL_EXCEPTION.txt in this package.
+**
+** Qt for Windows(R) Licensees
+** As a special exception, Nokia, as the sole copyright holder for Qt
+** Designer, grants users of the Qt/Eclipse Integration plug-in the
+** right for the Qt/Eclipse Integration to link to functionality
+** provided by Qt Designer and its related libraries.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+/*
+ These functions are based on Peter J. Weinberger's hash function
+ (from the Dragon Book). The constant 24 in the original function
+ was replaced with 23 to produce fewer collisions on input such as
+ "a", "aa", "aaa", "aaaa", ...
+*/
+
+// adjustment to null-terminated strings
+// (c) 2008 Klarälvdalens Datakonsult AB
+static uint hash(const uchar *p)
+{
+ uint h = 0;
+ uint g;
+
+ while (*p) {
+ h = (h << 4) + *p++;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h ^= g >> 23;
+ }
+ h &= ~g;
+ }
+ return h;
+}
+
+//
+// end Nokia-copyrighted code
+//
+
+static inline uint qHash(const char *data)
+{
+ if (!data) {
+ return 1; // something != 0
+ }
+ return ::hash(reinterpret_cast<const uchar *>(data));
+}
+
+class AbstractKeyListModel::Private
+{
+public:
+ Private() :
+ m_toolTipOptions(Formatting::Validity),
+ m_useKeyCache(false),
+ m_secretOnly(false) {}
+ int m_toolTipOptions;
+ mutable QHash<const char *, QVariant> prettyEMailCache;
+ bool m_useKeyCache;
+ bool m_secretOnly;
+};
+AbstractKeyListModel::AbstractKeyListModel(QObject *p)
+ : QAbstractItemModel(p), KeyListModelInterface(), d(new Private)
+{
+
+}
+
+AbstractKeyListModel::~AbstractKeyListModel() {}
+
+void AbstractKeyListModel::setToolTipOptions(int opts)
+{
+ d->m_toolTipOptions = opts;
+}
+
+int AbstractKeyListModel::toolTipOptions() const
+{
+ return d->m_toolTipOptions;
+}
+
+Key AbstractKeyListModel::key(const QModelIndex &idx) const
+{
+ if (idx.isValid()) {
+ return doMapToKey(idx);
+ } else {
+ return Key::null;
+ }
+}
+
+std::vector<Key> AbstractKeyListModel::keys(const QList<QModelIndex> &indexes) const
+{
+ std::vector<Key> result;
+ result.reserve(indexes.size());
+ std::transform(indexes.begin(), indexes.end(),
+ std::back_inserter(result),
+ boost::bind(&AbstractKeyListModel::key, this, _1));
+ result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
+ return result;
+}
+
+QModelIndex AbstractKeyListModel::index(const Key &key, int col) const
+{
+ if (key.isNull() || col < 0 || col >= NumColumns) {
+ return QModelIndex();
+ } else {
+ return doMapFromKey(key, col);
+ }
+}
+
+QList<QModelIndex> AbstractKeyListModel::indexes(const std::vector<Key> &keys) const
+{
+ QList<QModelIndex> result;
+ std::transform(keys.begin(), keys.end(),
+ std::back_inserter(result),
+ // if some compilers are complaining about ambiguous overloads, use this line instead:
+ //bind( static_cast<QModelIndex(AbstractKeyListModel::*)(const Key&,int)const>( &AbstractKeyListModel::index ), this, _1, 0 ) );
+ boost::bind(&AbstractKeyListModel::index, this, _1, 0));
+ return result;
+}
+
+void AbstractKeyListModel::setKeys(const std::vector<Key> &keys)
+{
+ clear();
+ addKeys(keys);
+}
+
+QModelIndex AbstractKeyListModel::addKey(const Key &key)
+{
+ const std::vector<Key> vec(1, key);
+ const QList<QModelIndex> l = doAddKeys(vec);
+ return l.empty() ? QModelIndex() : l.front();
+}
+
+void AbstractKeyListModel::removeKey(const Key &key)
+{
+ if (key.isNull()) {
+ return;
+ }
+ doRemoveKey(key);
+ d->prettyEMailCache.remove(key.primaryFingerprint());
+}
+
+QList<QModelIndex> AbstractKeyListModel::addKeys(const std::vector<Key> &keys)
+{
+ std::vector<Key> sorted;
+ sorted.reserve(keys.size());
+ std::remove_copy_if(keys.begin(), keys.end(),
+ std::back_inserter(sorted),
+ boost::bind(&Key::isNull, _1));
+ std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
+ return doAddKeys(sorted);
+}
+
+void AbstractKeyListModel::clear()
+{
+ beginResetModel();
+ doClear();
+ d->prettyEMailCache.clear();
+ endResetModel();
+}
+
+int AbstractKeyListModel::columnCount(const QModelIndex &) const
+{
+ return NumColumns;
+}
+
+QVariant AbstractKeyListModel::headerData(int section, Qt::Orientation o, int role) const
+{
+ if (o == Qt::Horizontal)
+ if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole)
+ switch (section) {
+ case PrettyName: return i18n("Name");
+ case PrettyEMail: return i18n("E-Mail");
+ case ValidFrom: return i18n("Valid From");
+ case ValidUntil: return i18n("Valid Until");
+ case TechnicalDetails: return i18n("Details");
+ case ShortKeyID: return i18n("Key-ID");
+ case NumColumns:;
+ }
+ return QVariant();
+}
+
+static QVariant returnIfValid(const QColor &t)
+{
+ if (t.isValid()) {
+ return t;
+ } else {
+ return QVariant();
+ }
+}
+
+static QVariant returnIfValid(const QIcon &t)
+{
+ if (!t.isNull()) {
+ return t;
+ } else {
+ return QVariant();
+ }
+}
+
+QVariant AbstractKeyListModel::data(const QModelIndex &index, int role) const
+{
+ const Key key = this->key(index);
+ if (key.isNull()) {
+ return QVariant();
+ }
+
+ const int column = index.column();
+
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ switch (column) {
+ case PrettyName:
+ return Formatting::prettyName(key);
+ case PrettyEMail:
+ if (const char *const fpr = key.primaryFingerprint()) {
+ const QHash<const char *, QVariant>::const_iterator it = d->prettyEMailCache.constFind(fpr);
+ if (it != d->prettyEMailCache.constEnd()) {
+ return *it;
+ } else {
+ return d->prettyEMailCache[fpr] = Formatting::prettyEMail(key);
+ }
+ } else {
+ return QVariant();
+ }
+ case ValidFrom:
+ if (role == Qt::EditRole) {
+ return Formatting::creationDate(key);
+ } else {
+ return Formatting::creationDateString(key);
+ }
+ case ValidUntil:
+ if (role == Qt::EditRole) {
+ return Formatting::expirationDate(key);
+ } else {
+ return Formatting::expirationDateString(key);
+ }
+ case TechnicalDetails:
+ return Formatting::type(key);
+ case ShortKeyID:
+ return QString::fromLatin1(key.shortKeyID());
+ case Summary:
+ return Formatting::summaryLine(key);
+ case NumColumns:
+ break;
+ }
+ } else if (role == Qt::ToolTipRole) {
+ return Formatting::toolTip(key, toolTipOptions());
+ } else if (role == Qt::FontRole) {
+ return KeyFilterManager::instance()->font(key, (column == ShortKeyID) ? QFont(QStringLiteral("courier")) : QFont());
+ } else if (role == Qt::DecorationRole) {
+ return column == Icon ? returnIfValid(KeyFilterManager::instance()->icon(key)) : QVariant();
+ } else if (role == Qt::BackgroundRole) {
+ return returnIfValid(KeyFilterManager::instance()->bgColor(key));
+ } else if (role == Qt::ForegroundRole) {
+ return returnIfValid(KeyFilterManager::instance()->fgColor(key));
+ } else if (role == FingerprintRole) {
+ return QString::fromLatin1(key.primaryFingerprint());
+ } else if (role == KeyRole) {
+ return QVariant::fromValue(key);
+ }
+ return QVariant();
+}
+
+namespace
+{
+template <typename Base>
+class TableModelMixin : public Base
+{
+public:
+ explicit TableModelMixin(QObject *p = Q_NULLPTR) : Base(p) {}
+ ~TableModelMixin() {}
+
+ using Base::index;
+ QModelIndex index(int row, int column, const QModelIndex &pidx = QModelIndex()) const Q_DECL_OVERRIDE
+ {
+ return this->hasIndex(row, column, pidx) ? this->createIndex(row, column, Q_NULLPTR) : QModelIndex();
+ }
+
+private:
+ QModelIndex parent(const QModelIndex &) const Q_DECL_OVERRIDE
+ {
+ return QModelIndex();
+ }
+ bool hasChildren(const QModelIndex &pidx) const Q_DECL_OVERRIDE
+ {
+ return (pidx.model() == this || !pidx.isValid()) && this->rowCount(pidx) > 0 && this->columnCount(pidx) > 0;
+ }
+};
+
+class FlatKeyListModel
+#ifndef Q_MOC_RUN
+ : public TableModelMixin<AbstractKeyListModel>
+#else
+ : public AbstractKeyListModel
+#endif
+{
+ Q_OBJECT
+public:
+ explicit FlatKeyListModel(QObject *parent = Q_NULLPTR);
+ ~FlatKeyListModel();
+
+ int rowCount(const QModelIndex &pidx) const Q_DECL_OVERRIDE
+ {
+ return pidx.isValid() ? 0 : mKeysByFingerprint.size();
+ }
+
+private:
+ Key doMapToKey(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ QModelIndex doMapFromKey(const Key &key, int col) const Q_DECL_OVERRIDE;
+ QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) Q_DECL_OVERRIDE;
+ void doRemoveKey(const Key &key) Q_DECL_OVERRIDE;
+ void doClear() Q_DECL_OVERRIDE {
+ mKeysByFingerprint.clear();
+ }
+
+private:
+ std::vector<Key> mKeysByFingerprint;
+};
+
+class HierarchicalKeyListModel : public AbstractKeyListModel
+{
+ Q_OBJECT
+public:
+ explicit HierarchicalKeyListModel(QObject *parent = Q_NULLPTR);
+ ~HierarchicalKeyListModel();
+
+ int rowCount(const QModelIndex &pidx) const Q_DECL_OVERRIDE;
+ using AbstractKeyListModel::index;
+ QModelIndex index(int row, int col, const QModelIndex &pidx) const Q_DECL_OVERRIDE;
+ QModelIndex parent(const QModelIndex &idx) const Q_DECL_OVERRIDE;
+
+ bool hasChildren(const QModelIndex &pidx) const Q_DECL_OVERRIDE
+ {
+ return rowCount(pidx) > 0;
+ }
+
+private:
+ Key doMapToKey(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ QModelIndex doMapFromKey(const Key &key, int col) const Q_DECL_OVERRIDE;
+ QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) Q_DECL_OVERRIDE;
+ void doRemoveKey(const Key &key) Q_DECL_OVERRIDE;
+ void doClear() Q_DECL_OVERRIDE {
+ mTopLevels.clear();
+ mKeysByFingerprint.clear();
+ mKeysByExistingParent.clear();
+ mKeysByNonExistingParent.clear();
+ }
+
+private:
+ void addTopLevelKey(const Key &key);
+ void addKeyWithParent(const char *issuer_fpr, const Key &key);
+ void addKeyWithoutParent(const char *issuer_fpr, const Key &key);
+
+private:
+ typedef std::map< std::string, std::vector<Key> > Map;
+ std::vector<Key> mKeysByFingerprint; // all keys
+ Map mKeysByExistingParent, mKeysByNonExistingParent; // parent->child map
+ std::vector<Key> mTopLevels; // all roots + parent-less
+};
+
+static const char *cleanChainID(const Key &key)
+{
+ if (key.isRoot()) {
+ return "";
+ }
+ if (const char *chid = key.chainID()) {
+ return chid;
+ }
+ return "";
+}
+
+}
+
+FlatKeyListModel::FlatKeyListModel(QObject *p)
+ : TableModelMixin<AbstractKeyListModel>(p),
+ mKeysByFingerprint()
+{
+
+}
+
+FlatKeyListModel::~FlatKeyListModel() {}
+
+Key FlatKeyListModel::doMapToKey(const QModelIndex &idx) const
+{
+ assert(idx.isValid());
+ if (static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() && idx.column() < NumColumns) {
+ return mKeysByFingerprint[ idx.row() ];
+ } else {
+ return Key::null;
+ }
+}
+
+QModelIndex FlatKeyListModel::doMapFromKey(const Key &key, int col) const
+{
+ assert(!key.isNull());
+ const std::vector<Key>::const_iterator it
+ = std::lower_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(),
+ key, _detail::ByFingerprint<std::less>());
+ if (it == mKeysByFingerprint.end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
+ return QModelIndex();
+ } else {
+ return createIndex(it - mKeysByFingerprint.begin(), col);
+ }
+}
+
+QList<QModelIndex> FlatKeyListModel::doAddKeys(const std::vector<Key> &keys)
+{
+#ifdef __GLIBCXX__
+ assert(__gnu_cxx::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
+#endif
+ if (keys.empty()) {
+ return QList<QModelIndex>();
+ }
+
+ for (std::vector<Key>::const_iterator it = keys.begin(), end = keys.end(); it != end; ++it) {
+
+ // find an insertion point:
+ const std::vector<Key>::iterator pos = std::upper_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), *it, _detail::ByFingerprint<std::less>());
+ const unsigned int idx = std::distance(mKeysByFingerprint.begin(), pos);
+
+ if (idx > 0 && qstrcmp(mKeysByFingerprint[idx - 1].primaryFingerprint(), it->primaryFingerprint()) == 0) {
+ // key existed before - replace with new one:
+ mKeysByFingerprint[idx - 1] = *it;
+ Q_EMIT dataChanged(createIndex(idx - 1, 0), createIndex(idx - 1, NumColumns - 1));
+ } else {
+ // new key - insert:
+ beginInsertRows(QModelIndex(), idx, idx);
+ mKeysByFingerprint.insert(pos, *it);
+ endInsertRows();
+ }
+ }
+
+ return indexes(keys);
+}
+
+void FlatKeyListModel::doRemoveKey(const Key &key)
+{
+ const std::vector<Key>::iterator it
+ = qBinaryFind(mKeysByFingerprint.begin(), mKeysByFingerprint.end(),
+ key, _detail::ByFingerprint<std::less>());
+ if (it == mKeysByFingerprint.end()) {
+ return;
+ }
+
+ const unsigned int row = std::distance(mKeysByFingerprint.begin(), it);
+ beginRemoveRows(QModelIndex(), row, row);
+ mKeysByFingerprint.erase(it);
+ endRemoveRows();
+}
+
+HierarchicalKeyListModel::HierarchicalKeyListModel(QObject *p)
+ : AbstractKeyListModel(p),
+ mKeysByFingerprint(),
+ mKeysByExistingParent(),
+ mKeysByNonExistingParent(),
+ mTopLevels()
+{
+
+}
+
+HierarchicalKeyListModel::~HierarchicalKeyListModel() {}
+
+int HierarchicalKeyListModel::rowCount(const QModelIndex &pidx) const
+{
+
+ // toplevel item:
+ if (!pidx.isValid()) {
+ return mTopLevels.size();
+ }
+
+ if (pidx.column() != 0) {
+ return 0;
+ }
+
+ // non-toplevel item - find the number of subjects for this issuer:
+ const Key issuer = this->key(pidx);
+ const char *const fpr = issuer.primaryFingerprint();
+ if (!fpr || !*fpr) {
+ return 0;
+ }
+ const Map::const_iterator it = mKeysByExistingParent.find(fpr);
+ if (it == mKeysByExistingParent.end()) {
+ return 0;
+ }
+ return it->second.size();
+}
+
+QModelIndex HierarchicalKeyListModel::index(int row, int col, const QModelIndex &pidx) const
+{
+
+ if (row < 0 || col < 0 || col >= NumColumns) {
+ return QModelIndex();
+ }
+
+ // toplevel item:
+ if (!pidx.isValid()) {
+ if (static_cast<unsigned>(row) < mTopLevels.size()) {
+ return index(mTopLevels[row], col);
+ } else {
+ return QModelIndex();
+ }
+ }
+
+ // non-toplevel item - find the row'th subject of this key:
+ const Key issuer = this->key(pidx);
+ const char *const fpr = issuer.primaryFingerprint();
+ if (!fpr || !*fpr) {
+ return QModelIndex();
+ }
+ const Map::const_iterator it = mKeysByExistingParent.find(fpr);
+ if (it == mKeysByExistingParent.end() || static_cast<unsigned>(row) >= it->second.size()) {
+ return QModelIndex();
+ }
+ return index(it->second[row], col);
+}
+
+QModelIndex HierarchicalKeyListModel::parent(const QModelIndex &idx) const
+{
+ const Key key = this->key(idx);
+ if (key.isNull() || key.isRoot()) {
+ return QModelIndex();
+ }
+ const std::vector<Key>::const_iterator it
+ = qBinaryFind(mKeysByFingerprint.begin(), mKeysByFingerprint.end(),
+ cleanChainID(key), _detail::ByFingerprint<std::less>());
+ return it != mKeysByFingerprint.end() ? index(*it) : QModelIndex();
+}
+
+Key HierarchicalKeyListModel::doMapToKey(const QModelIndex &idx) const
+{
+
+ if (!idx.isValid()) {
+ return Key::null;
+ }
+
+ const char *const issuer_fpr = static_cast<const char *>(idx.internalPointer());
+ if (!issuer_fpr || !*issuer_fpr) {
+ // top-level:
+ if (static_cast<unsigned>(idx.row()) >= mTopLevels.size()) {
+ return Key::null;
+ } else {
+ return mTopLevels[idx.row()];
+ }
+ }
+
+ // non-toplevel:
+ const Map::const_iterator it
+ = mKeysByExistingParent.find(issuer_fpr);
+ if (it == mKeysByExistingParent.end() || static_cast<unsigned>(idx.row()) >= it->second.size()) {
+ return Key::null;
+ }
+ return it->second[idx.row()];
+}
+
+QModelIndex HierarchicalKeyListModel::doMapFromKey(const Key &key, int col) const
+{
+
+ if (key.isNull()) {
+ return QModelIndex();
+ }
+
+ const char *issuer_fpr = cleanChainID(key);
+
+ // we need to look in the toplevels list,...
+ const std::vector<Key> *v = &mTopLevels;
+ if (issuer_fpr && *issuer_fpr) {
+ const std::map< std::string, std::vector<Key> >::const_iterator it
+ = mKeysByExistingParent.find(issuer_fpr);
+ // ...unless we find an existing parent:
+ if (it != mKeysByExistingParent.end()) {
+ v = &it->second;
+ } else {
+ issuer_fpr = 0; // force internalPointer to zero for toplevels
+ }
+ }
+
+ const std::vector<Key>::const_iterator it
+ = std::lower_bound(v->begin(), v->end(), key, _detail::ByFingerprint<std::less>());
+ if (it == v->end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
+ return QModelIndex();
+ }
+
+ const unsigned int row = std::distance(v->begin(), it);
+ return createIndex(row, col, const_cast<char * /* thanks, Trolls :/ */ >(issuer_fpr));
+}
+
+void HierarchicalKeyListModel::addKeyWithParent(const char *issuer_fpr, const Key &key)
+{
+
+ assert(issuer_fpr); assert(*issuer_fpr); assert(!key.isNull());
+
+ std::vector<Key> &subjects = mKeysByExistingParent[issuer_fpr];
+
+ // find insertion point:
+ const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
+ const int row = std::distance(subjects.begin(), it);
+
+ if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
+ // exists -> replace
+ *it = key;
+ Q_EMIT dataChanged(createIndex(row, 0, const_cast<char *>(issuer_fpr)), createIndex(row, NumColumns - 1, const_cast<char *>(issuer_fpr)));
+ } else {
+ // doesn't exist -> insert
+ const std::vector<Key>::const_iterator pos = qBinaryFind(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
+ assert(pos != mKeysByFingerprint.end());
+ beginInsertRows(index(*pos), row, row);
+ subjects.insert(it, key);
+ endInsertRows();
+ }
+}
+
+void HierarchicalKeyListModel::addKeyWithoutParent(const char *issuer_fpr, const Key &key)
+{
+
+ assert(issuer_fpr); assert(*issuer_fpr); assert(!key.isNull());
+
+ std::vector<Key> &subjects = mKeysByNonExistingParent[issuer_fpr];
+
+ // find insertion point:
+ const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
+
+ if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0)
+ // exists -> replace
+ {
+ *it = key;
+ } else
+ // doesn't exist -> insert
+ {
+ subjects.insert(it, key);
+ }
+
+ addTopLevelKey(key);
+}
+
+void HierarchicalKeyListModel::addTopLevelKey(const Key &key)
+{
+
+ // find insertion point:
+ const std::vector<Key>::iterator it = std::lower_bound(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
+ const int row = std::distance(mTopLevels.begin(), it);
+
+ if (it != mTopLevels.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
+ // exists -> replace
+ *it = key;
+ Q_EMIT dataChanged(createIndex(row, 0), createIndex(row, NumColumns - 1));
+ } else {
+ // doesn't exist -> insert
+ beginInsertRows(QModelIndex(), row, row);
+ mTopLevels.insert(it, key);
+ endInsertRows();
+ }
+
+}
+
+// sorts 'keys' such that parent always come before their children:
+static std::vector<Key> topological_sort(const std::vector<Key> &keys)
+{
+
+ boost::adjacency_list<> graph(keys.size());
+
+ // add edges from children to parents:
+ for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
+ const char *const issuer_fpr = cleanChainID(keys[i]);
+ if (!issuer_fpr || !*issuer_fpr) {
+ continue;
+ }
+ const std::vector<Key>::const_iterator it
+ = qBinaryFind(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
+ if (it == keys.end()) {
+ continue;
+ }
+ add_edge(i, std::distance(keys.begin(), it), graph);
+ }
+
+ std::vector<int> order;
+ order.reserve(keys.size());
+ topological_sort(graph, std::back_inserter(order));
+
+ assert(order.size() == keys.size());
+
+ std::vector<Key> result;
+ result.reserve(keys.size());
+ Q_FOREACH (int i, order) {
+ result.push_back(keys[i]);
+ }
+ return result;
+}
+
+QList<QModelIndex> HierarchicalKeyListModel::doAddKeys(const std::vector<Key> &keys)
+{
+#ifdef __GLIBCXX__
+ assert(__gnu_cxx::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
+#endif
+ if (keys.empty()) {
+ return QList<QModelIndex>();
+ }
+
+ const std::vector<Key> oldKeys = mKeysByFingerprint;
+
+ std::vector<Key> merged;
+ merged.reserve(keys.size() + mKeysByFingerprint.size());
+ std::set_union(keys.begin(), keys.end(),
+ mKeysByFingerprint.begin(), mKeysByFingerprint.end(),
+ std::back_inserter(merged), _detail::ByFingerprint<std::less>());
+
+ mKeysByFingerprint = merged;
+
+ std::set<Key, _detail::ByFingerprint<std::less> > changedParents;
+
+ Q_FOREACH (const Key &key, topological_sort(keys)) {
+
+ // check to see whether this key is a parent for a previously parent-less group:
+ const char *const fpr = key.primaryFingerprint();
+ if (!fpr || !*fpr) {
+ continue;
+ }
+
+ const bool keyAlreadyExisted = qBinaryFind(oldKeys.begin(), oldKeys.end(), key, _detail::ByFingerprint<std::less>()) != oldKeys.end();
+
+ const Map::iterator it = mKeysByNonExistingParent.find(fpr);
+ const std::vector<Key> children = it != mKeysByNonExistingParent.end() ? it->second : std::vector<Key>();
+ if (it != mKeysByNonExistingParent.end()) {
+ mKeysByNonExistingParent.erase(it);
+ }
+
+ // Step 1: For new keys, remove children from toplevel:
+
+ if (!keyAlreadyExisted) {
+ std::vector<Key>::iterator last = mTopLevels.begin();
+ std::vector<Key>::iterator lastFP = mKeysByFingerprint.begin();
+
+ Q_FOREACH (const Key &k, children) {
+ last = qBinaryFind(last, mTopLevels.end(), k, _detail::ByFingerprint<std::less>());
+ assert(last != mTopLevels.end());
+ const int row = std::distance(mTopLevels.begin(), last);
+
+ lastFP = qBinaryFind(lastFP, mKeysByFingerprint.end(), k, _detail::ByFingerprint<std::less>());
+ assert(lastFP != mKeysByFingerprint.end());
+
+ Q_EMIT rowAboutToBeMoved(QModelIndex(), row);
+ beginRemoveRows(QModelIndex(), row, row);
+ last = mTopLevels.erase(last);
+ lastFP = mKeysByFingerprint.erase(lastFP);
+ endRemoveRows();
+ }
+ }
+ // Step 2: add/update key
+
+ const char *const issuer_fpr = cleanChainID(key);
+ if (!issuer_fpr || !*issuer_fpr)
+ // root or something...
+ {
+ addTopLevelKey(key);
+ } else if (std::binary_search(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>()))
+ // parent exists...
+ {
+ addKeyWithParent(issuer_fpr, key);
+ } else
+ // parent does't exist yet...
+ {
+ addKeyWithoutParent(issuer_fpr, key);
+ }
+
+ const QModelIndex key_idx = index(key);
+ QModelIndex key_parent = key_idx.parent();
+ while (key_parent.isValid()) {
+ changedParents.insert(doMapToKey(key_parent));
+ key_parent = key_parent.parent();
+ }
+
+ // Step 3: Add children to new parent ( == key )
+
+ if (!keyAlreadyExisted && !children.empty()) {
+ addKeys(children);
+ const QModelIndex new_parent = index(key);
+ // Q_EMIT the rowMoved() signals in reversed direction, so the
+ // implementation can use a stack for mapping.
+ for (int i = children.size() - 1; i >= 0; --i) {
+ Q_EMIT rowMoved(new_parent, i);
+ }
+ }
+ }
+ //Q_EMIT dataChanged for all parents with new children. This triggers KeyListSortFilterProxyModel to
+ //show a parent node if it just got children matching the proxy's filter
+ Q_FOREACH (const Key &i, changedParents) {
+ const QModelIndex idx = index(i);
+ if (idx.isValid()) {
+ Q_EMIT dataChanged(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), NumColumns - 1));
+ }
+ }
+ return indexes(keys);
+}
+
+void HierarchicalKeyListModel::doRemoveKey(const Key &key)
+{
+ const QModelIndex idx = index(key);
+ if (!idx.isValid()) {
+ return;
+ }
+
+ const char *const fpr = key.primaryFingerprint();
+ if (mKeysByExistingParent.find(fpr) != mKeysByExistingParent.end()) {
+ //handle non-leave nodes:
+ std::vector<Key> keys = mKeysByFingerprint;
+ const std::vector<Key>::iterator it = qBinaryFind(keys.begin(), keys.end(),
+ key, _detail::ByFingerprint<std::less>());
+ if (it == keys.end()) {
+ return;
+ }
+ keys.erase(it);
+ // FIXME for simplicity, we just clear the model and re-add all keys minus the removed one. This is suboptimal,
+ // but acceptable given that deletion of non-leave nodes is rather rare.
+ clear();
+ addKeys(keys);
+ return;
+ }
+
+ //handle leave nodes:
+
+ const std::vector<Key>::iterator it = qBinaryFind(mKeysByFingerprint.begin(), mKeysByFingerprint.end(),
+ key, _detail::ByFingerprint<std::less>());
+
+ assert(it != mKeysByFingerprint.end());
+ assert(mKeysByNonExistingParent.find(fpr) == mKeysByNonExistingParent.end());
+ assert(mKeysByExistingParent.find(fpr) == mKeysByExistingParent.end());
+
+ beginRemoveRows(parent(idx), idx.row(), idx.row());
+ mKeysByFingerprint.erase(it);
+
+ const char *const issuer_fpr = cleanChainID(key);
+
+ const std::vector<Key>::iterator tlIt = qBinaryFind(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
+ if (tlIt != mTopLevels.end()) {
+ mTopLevels.erase(tlIt);
+ }
+
+ if (issuer_fpr && *issuer_fpr) {
+ const Map::iterator nexIt = mKeysByNonExistingParent.find(issuer_fpr);
+ if (nexIt != mKeysByNonExistingParent.end()) {
+ const std::vector<Key>::iterator eit = qBinaryFind(nexIt->second.begin(), nexIt->second.end(), key, _detail::ByFingerprint<std::less>());
+ if (eit != nexIt->second.end()) {
+ nexIt->second.erase(eit);
+ }
+ if (nexIt->second.empty()) {
+ mKeysByNonExistingParent.erase(nexIt);
+ }
+ }
+
+ const Map::iterator exIt = mKeysByExistingParent.find(issuer_fpr);
+ if (exIt != mKeysByExistingParent.end()) {
+ const std::vector<Key>::iterator eit = qBinaryFind(exIt->second.begin(), exIt->second.end(), key, _detail::ByFingerprint<std::less>());
+ if (eit != exIt->second.end()) {
+ exIt->second.erase(eit);
+ }
+ if (exIt->second.empty()) {
+ mKeysByExistingParent.erase(exIt);
+ }
+ }
+ }
+ endRemoveRows();
+}
+
+void AbstractKeyListModel::useKeyCache(bool value, bool secretOnly)
+{
+ d->m_secretOnly = secretOnly;
+ d->m_useKeyCache = value;
+ if (value) {
+ setKeys(d->m_secretOnly ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys());
+ } else {
+ setKeys(std::vector<Key>());
+ }
+ connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged,
+ this, [this] {
+ if (d->m_useKeyCache) {
+ setKeys(d->m_secretOnly ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys());
+ }
+ });
+}
+
+// static
+AbstractKeyListModel *AbstractKeyListModel::createFlatKeyListModel(QObject *p)
+{
+ AbstractKeyListModel *const m = new FlatKeyListModel(p);
+#ifdef KLEO_MODEL_TEST
+ new ModelTest(m, p);
+#endif
+ return m;
+}
+
+// static
+AbstractKeyListModel *AbstractKeyListModel::createHierarchicalKeyListModel(QObject *p)
+{
+ AbstractKeyListModel *const m = new HierarchicalKeyListModel(p);
+#ifdef KLEO_MODEL_TEST
+ new ModelTest(m, p);
+#endif
+ return m;
+}
+
+#include "keylistmodel.moc"
+
+/*!
+ \fn AbstractKeyListModel::rowAboutToBeMoved( const QModelIndex & old_parent, int old_row )
+
+ Emitted before the removal of a row from that model. It will later
+ be added to the model again, in response to which rowMoved() will be
+ emitted. If multiple rows are moved in one go, multiple
+ rowAboutToBeMoved() signals are emitted before the corresponding
+ number of rowMoved() signals is emitted - in reverse order.
+
+ This works around the absence of move semantics in
+ QAbstractItemModel. Clients can maintain a stack to perform the
+ QModelIndex-mapping themselves, or, e.g., to preserve the selection
+ status of the row:
+
+ \code
+ std::vector<bool> mMovingRowWasSelected; // transient, used when rows are moved
+ // ...
+ void slotRowAboutToBeMoved( const QModelIndex & p, int row ) {
+ mMovingRowWasSelected.push_back( selectionModel()->isSelected( model()->index( row, 0, p ) ) );
+ }
+ void slotRowMoved( const QModelIndex & p, int row ) {
+ const bool wasSelected = mMovingRowWasSelected.back();
+ mMovingRowWasSelected.pop_back();
+ if ( wasSelected )
+ selectionModel()->select( model()->index( row, 0, p ), Select|Rows );
+ }
+ \endcode
+
+ A similar mechanism could be used to preserve the current item during moves.
+*/
+
+/*!
+ \fn AbstractKeyListModel::rowMoved( const QModelIndex & new_parent, int new_parent )
+
+ See rowAboutToBeMoved()
+*/
diff --git a/src/models/keylistmodel.h b/src/models/keylistmodel.h
new file mode 100644
index 0000000..97ae087
--- /dev/null
+++ b/src/models/keylistmodel.h
@@ -0,0 +1,113 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keylistmodel.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef __KLEOPATRA_MODELS_KEYLISTMODEL_H__
+#define __KLEOPATRA_MODELS_KEYLISTMODEL_H__
+
+#include <QAbstractItemModel>
+
+#include <kleo_export.h>
+
+#include "keylistmodelinterface.h"
+
+#include <vector>
+
+namespace GpgME
+{
+class Key;
+}
+
+namespace Kleo
+{
+
+class KLEO_EXPORT AbstractKeyListModel : public QAbstractItemModel
+ , public KeyListModelInterface
+{
+ Q_OBJECT
+public:
+ explicit AbstractKeyListModel(QObject *parent = Q_NULLPTR);
+ ~AbstractKeyListModel();
+
+ static AbstractKeyListModel *createFlatKeyListModel(QObject *parent = Q_NULLPTR);
+ static AbstractKeyListModel *createHierarchicalKeyListModel(QObject *parent = Q_NULLPTR);
+
+ GpgME::Key key(const QModelIndex &idx) const Q_DECL_OVERRIDE;
+ std::vector<GpgME::Key> keys(const QList<QModelIndex> &indexes) const Q_DECL_OVERRIDE;
+
+ using QAbstractItemModel::index;
+ QModelIndex index(const GpgME::Key &key) const Q_DECL_OVERRIDE
+ {
+ return index(key, 0);
+ }
+ QModelIndex index(const GpgME::Key &key, int col) const;
+ QList<QModelIndex> indexes(const std::vector<GpgME::Key> &keys) const Q_DECL_OVERRIDE;
+
+Q_SIGNALS:
+ void rowAboutToBeMoved(const QModelIndex &old_parent, int old_row);
+ void rowMoved(const QModelIndex &new_parent, int new_row);
+
+public Q_SLOTS:
+ void setKeys(const std::vector<GpgME::Key> &keys);
+ /* Set this to set all or only secret keys from the keycache. */
+ void useKeyCache(bool value, bool secretOnly);
+ QModelIndex addKey(const GpgME::Key &key);
+ QList<QModelIndex> addKeys(const std::vector<GpgME::Key> &keys);
+ void removeKey(const GpgME::Key &key);
+ void clear();
+
+public:
+ int columnCount(const QModelIndex &pidx) const Q_DECL_OVERRIDE;
+ QVariant headerData(int section, Qt::Orientation o, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+
+ /**
+ * defines which information is displayed in tooltips
+ * see Kleo::Formatting::ToolTipOption
+ */
+ int toolTipOptions() const;
+
+ void setToolTipOptions(int opts);
+
+private:
+ virtual GpgME::Key doMapToKey(const QModelIndex &index) const = 0;
+ virtual QModelIndex doMapFromKey(const GpgME::Key &key, int column) const = 0;
+ virtual QList<QModelIndex> doAddKeys(const std::vector<GpgME::Key> &keys) = 0;
+ virtual void doRemoveKey(const GpgME::Key &key) = 0;
+ virtual void doClear() = 0;
+
+private:
+ class Private;
+ QScopedPointer<Private> const d;
+};
+
+}
+
+#endif /* __KLEOPATRA_MODELS_KEYLISTMODEL_H__ */
diff --git a/src/models/keylistmodelinterface.h b/src/models/keylistmodelinterface.h
new file mode 100644
index 0000000..b786780
--- /dev/null
+++ b/src/models/keylistmodelinterface.h
@@ -0,0 +1,88 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keylistmodelinterface.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2008 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KLEOPATRA_MODELS_KEYLISTMODELINTERFACE_H__
+#define __KLEOPATRA_MODELS_KEYLISTMODELINTERFACE_H__
+
+#include <vector>
+
+namespace GpgME
+{
+class Key;
+}
+
+class QModelIndex;
+template <typename T> class QList;
+
+namespace Kleo
+{
+
+class KeyListModelInterface
+{
+public:
+ virtual ~KeyListModelInterface() {}
+
+ static const int FingerprintRole = 0xF1;
+ static const int KeyRole = 0xF2;
+
+ enum Columns {
+ PrettyName,
+ PrettyEMail,
+ ValidFrom,
+ ValidUntil,
+ TechnicalDetails,
+ /* OpenPGP only, really */
+ ShortKeyID,
+ Summary, // Short summary line
+#if 0
+ Fingerprint,
+ LongKeyID,
+ /* X509 only, really */
+ Issuer,
+ Subject,
+ SerialNumber,
+#endif
+
+ NumColumns,
+ Icon = PrettyName // which column shall the icon be displayed in?
+ };
+
+ virtual GpgME::Key key(const QModelIndex &idx) const = 0;
+ virtual std::vector<GpgME::Key> keys(const QList<QModelIndex> &idxs) const = 0;
+
+ virtual QModelIndex index(const GpgME::Key &key) const = 0;
+ virtual QList<QModelIndex> indexes(const std::vector<GpgME::Key> &keys) const = 0;
+};
+
+}
+
+#endif /* __KLEOPATRA_MODELS_KEYLISTMODELINTERFACE_H__ */
diff --git a/src/models/keylistsortfilterproxymodel.cpp b/src/models/keylistsortfilterproxymodel.cpp
new file mode 100644
index 0000000..c7ed44d
--- /dev/null
+++ b/src/models/keylistsortfilterproxymodel.cpp
@@ -0,0 +1,218 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keylistsortfilterproxymodel.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "keylistsortfilterproxymodel.h"
+
+#include "keylistmodel.h"
+#include "kleo/keyfilter.h"
+#include "kleo/stl_util.h"
+
+#include <libkleo_debug.h>
+
+#include <gpgme++/key.h>
+
+#include <boost/bind.hpp>
+
+#include <cassert>
+
+using namespace Kleo;
+using namespace boost;
+using namespace GpgME;
+
+AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel(QObject *p)
+ : QSortFilterProxyModel(p), KeyListModelInterface()
+{
+ init();
+}
+
+AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel(const AbstractKeyListSortFilterProxyModel &other)
+ : QSortFilterProxyModel(), KeyListModelInterface()
+{
+ Q_UNUSED(other);
+ init();
+}
+
+void AbstractKeyListSortFilterProxyModel::init()
+{
+ setDynamicSortFilter(true);
+ setSortRole(Qt::EditRole); // EditRole can be expected to be in a less formatted way, better for sorting
+ setFilterRole(Qt::DisplayRole);
+ setFilterCaseSensitivity(Qt::CaseInsensitive);
+}
+
+AbstractKeyListSortFilterProxyModel::~AbstractKeyListSortFilterProxyModel() {}
+
+Key AbstractKeyListSortFilterProxyModel::key(const QModelIndex &idx) const
+{
+ const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel());
+ if (!klmi) {
+ static Key null;
+ return null;
+ }
+ return klmi->key(mapToSource(idx));
+}
+
+std::vector<Key> AbstractKeyListSortFilterProxyModel::keys(const QList<QModelIndex> &indexes) const
+{
+ const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel());
+ if (!klmi) {
+ return std::vector<Key>();
+ }
+ QList<QModelIndex> mapped;
+ std::transform(indexes.begin(), indexes.end(),
+ std::back_inserter(mapped),
+ boost::bind(&QAbstractProxyModel::mapToSource, this, _1));
+ return klmi->keys(mapped);
+}
+
+QModelIndex AbstractKeyListSortFilterProxyModel::index(const Key &key) const
+{
+ if (const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel())) {
+ return mapFromSource(klmi->index(key));
+ } else {
+ return QModelIndex();
+ }
+}
+
+QList<QModelIndex> AbstractKeyListSortFilterProxyModel::indexes(const std::vector<Key> &keys) const
+{
+ if (const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel())) {
+ const QList<QModelIndex> source = klmi->indexes(keys);
+ QList<QModelIndex> mapped;
+ std::transform(source.begin(), source.end(),
+ std::back_inserter(mapped),
+ boost::bind(&QAbstractProxyModel::mapFromSource, this, _1));
+ return mapped;
+ } else {
+ return QList<QModelIndex>();
+ }
+}
+
+class KeyListSortFilterProxyModel::Private
+{
+ friend class ::Kleo::KeyListSortFilterProxyModel;
+public:
+ explicit Private()
+ : keyFilter() {}
+ ~Private() {}
+
+private:
+ shared_ptr<const KeyFilter> keyFilter;
+};
+
+KeyListSortFilterProxyModel::KeyListSortFilterProxyModel(QObject *p)
+ : AbstractKeyListSortFilterProxyModel(p), d(new Private)
+{
+
+}
+
+KeyListSortFilterProxyModel::KeyListSortFilterProxyModel(const KeyListSortFilterProxyModel &other)
+ : AbstractKeyListSortFilterProxyModel(other), d(new Private(*other.d))
+{
+
+}
+
+KeyListSortFilterProxyModel::~KeyListSortFilterProxyModel() {}
+
+KeyListSortFilterProxyModel *KeyListSortFilterProxyModel::clone() const
+{
+ return new KeyListSortFilterProxyModel(*this);
+}
+
+shared_ptr<const KeyFilter> KeyListSortFilterProxyModel::keyFilter() const
+{
+ return d->keyFilter;
+}
+
+void KeyListSortFilterProxyModel::setKeyFilter(const shared_ptr<const KeyFilter> &kf)
+{
+ if (kf == d->keyFilter) {
+ return;
+ }
+ d->keyFilter = kf;
+ invalidateFilter();
+}
+
+bool KeyListSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
+{
+
+ //
+ // 0. Keep parents of matching children:
+ //
+ const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
+ for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i)
+ if (filterAcceptsRow(i, index)) {
+ return true;
+ }
+
+ //
+ // 1. Check filterRegExp
+ //
+ const int role = filterRole();
+ const int col = filterKeyColumn();
+ const QRegExp rx = filterRegExp();
+ const QModelIndex nameIndex = sourceModel()->index(source_row, PrettyName, source_parent);
+
+ if (col) {
+ const QModelIndex colIdx = sourceModel()->index(source_row, col, source_parent);
+ const QString content = colIdx.data(role).toString();
+ if (!content.contains(rx)) {
+ return false;
+ }
+ } else {
+ // By default match name and email
+ const QString name = nameIndex.data(role).toString();
+
+ const QModelIndex emailIndex = sourceModel()->index(source_row, PrettyEMail, source_parent);
+ const QString email = emailIndex.data(role).toString();
+
+ if (!name.contains(rx) && !email.contains(rx)) {
+ return false;
+ }
+ }
+
+ //
+ // 2. Check that key filters match (if any are defined)
+ //
+ if (d->keyFilter) { // avoid artifacts when no filters are defined
+
+ const KeyListModelInterface *const klm = dynamic_cast<KeyListModelInterface *>(sourceModel());
+ assert(klm);
+ const Key key = klm->key(nameIndex);
+
+ return d->keyFilter->matches(key, KeyFilter::Filtering);
+ }
+
+ // 3. match by default:
+ return true;
+}
+
diff --git a/src/models/keylistsortfilterproxymodel.h b/src/models/keylistsortfilterproxymodel.h
new file mode 100644
index 0000000..b9e279e
--- /dev/null
+++ b/src/models/keylistsortfilterproxymodel.h
@@ -0,0 +1,100 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/keylistsortfilterproxymodel.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef __KLEOPATRA_MODELS_KEYLISTSORTFILTERPROXYMODEL_H__
+#define __KLEOPATRA_MODELS_KEYLISTSORTFILTERPROXYMODEL_H__
+
+#include <QSortFilterProxyModel>
+
+#include "keylistmodelinterface.h"
+
+#include <kleo_export.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace GpgME
+{
+class Key;
+}
+
+namespace Kleo
+{
+
+class KeyFilter;
+
+class KLEO_EXPORT AbstractKeyListSortFilterProxyModel : public QSortFilterProxyModel
+ , public KeyListModelInterface
+{
+ Q_OBJECT
+protected:
+ AbstractKeyListSortFilterProxyModel(const AbstractKeyListSortFilterProxyModel &);
+public:
+ explicit AbstractKeyListSortFilterProxyModel(QObject *parent = Q_NULLPTR);
+ ~AbstractKeyListSortFilterProxyModel();
+
+ virtual AbstractKeyListSortFilterProxyModel *clone() const = 0;
+
+ GpgME::Key key(const QModelIndex &idx) const Q_DECL_OVERRIDE;
+ std::vector<GpgME::Key> keys(const QList<QModelIndex> &indexes) const Q_DECL_OVERRIDE;
+
+ using QAbstractItemModel::index;
+ QModelIndex index(const GpgME::Key &key) const Q_DECL_OVERRIDE;
+ QList<QModelIndex> indexes(const std::vector<GpgME::Key> &keys) const Q_DECL_OVERRIDE;
+
+private:
+ void init();
+};
+
+class KLEO_EXPORT KeyListSortFilterProxyModel : public AbstractKeyListSortFilterProxyModel
+{
+ Q_OBJECT
+protected:
+ KeyListSortFilterProxyModel(const KeyListSortFilterProxyModel &);
+public:
+ explicit KeyListSortFilterProxyModel(QObject *parent = Q_NULLPTR);
+ ~KeyListSortFilterProxyModel();
+
+ boost::shared_ptr<const KeyFilter> keyFilter() const;
+ void setKeyFilter(const boost::shared_ptr<const KeyFilter> &kf);
+
+ KeyListSortFilterProxyModel *clone() const Q_DECL_OVERRIDE;
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const Q_DECL_OVERRIDE;
+
+private:
+ class Private;
+ QScopedPointer<Private> const d;
+};
+
+}
+
+#endif /* __KLEOPATRA_MODELS_KEYLISTMODEL_H__ */
diff --git a/src/models/keyrearrangecolumnsproxymodel.cpp b/src/models/keyrearrangecolumnsproxymodel.cpp
new file mode 100644
index 0000000..8db3faf
--- /dev/null
+++ b/src/models/keyrearrangecolumnsproxymodel.cpp
@@ -0,0 +1,81 @@
+/* models/keyrearangecolumnsproxymodel.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2016 Intevation GmbH
+
+ 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#include "keyrearrangecolumnsproxymodel.h"
+
+#include <gpgme++/key.h>
+#include <cassert>
+
+using namespace Kleo;
+using namespace GpgME;
+
+KeyRearrangeColumnsProxyModel::KeyRearrangeColumnsProxyModel(QObject *parent) :
+ KRearrangeColumnsProxyModel(parent),
+ KeyListModelInterface()
+{
+
+}
+
+KeyListModelInterface *KeyRearrangeColumnsProxyModel::klm() const
+{
+ KeyListModelInterface *ret = dynamic_cast<KeyListModelInterface *>(sourceModel());
+ assert(ret);
+ return ret;
+}
+
+Key KeyRearrangeColumnsProxyModel::key(const QModelIndex &idx) const
+{
+ return klm()->key(mapToSource(idx));
+}
+
+std::vector<GpgME::Key> KeyRearrangeColumnsProxyModel::keys(const QList<QModelIndex> &idxs) const
+{
+ QList<QModelIndex> srcIdxs;
+ Q_FOREACH (const QModelIndex idx, idxs) {
+ srcIdxs << mapToSource(idx);
+ }
+ return klm()->keys(srcIdxs);
+}
+
+
+QModelIndex KeyRearrangeColumnsProxyModel::index(const GpgME::Key &key) const
+{
+ return mapFromSource(klm()->index(key));
+}
+
+QList<QModelIndex> KeyRearrangeColumnsProxyModel::indexes(const std::vector<GpgME::Key> &keys) const
+{
+ QList<QModelIndex> myIdxs;
+ const QList <QModelIndex> srcIdxs = klm()->indexes(keys);
+ Q_FOREACH (const QModelIndex idx, srcIdxs) {
+ myIdxs << mapFromSource(idx);
+ }
+ return myIdxs;
+}
diff --git a/src/models/keyrearrangecolumnsproxymodel.h b/src/models/keyrearrangecolumnsproxymodel.h
new file mode 100644
index 0000000..6e3ac91
--- /dev/null
+++ b/src/models/keyrearrangecolumnsproxymodel.h
@@ -0,0 +1,60 @@
+/* models/keyrearangecolumnsproxymodel.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2016 Intevation GmbH
+
+ 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef KEYREARRANGECOLUMNSPROXYMODEL_H
+#define KEYREARRANGECOLUMNSPROXYMODEL_H
+
+#include "keylistmodelinterface.h"
+
+#include <kleo_export.h>
+
+#include <KRearrangeColumnsProxyModel>
+
+namespace Kleo
+{
+/** KRearrangeColumnsProxymodel that implements the KeyListModelInterface. */
+class KLEO_EXPORT KeyRearrangeColumnsProxyModel: public KRearrangeColumnsProxyModel,
+ public KeyListModelInterface
+{
+public:
+ explicit KeyRearrangeColumnsProxyModel(QObject *parent = Q_NULLPTR);
+
+ GpgME::Key key(const QModelIndex &idx) const Q_DECL_OVERRIDE;
+ std::vector<GpgME::Key> keys(const QList<QModelIndex> &idxs) const Q_DECL_OVERRIDE;
+
+ using KRearrangeColumnsProxyModel::index;
+
+ QModelIndex index(const GpgME::Key &key) const Q_DECL_OVERRIDE;
+ QList<QModelIndex> indexes(const std::vector<GpgME::Key> &keys) const Q_DECL_OVERRIDE;
+private:
+ KeyListModelInterface *klm() const;
+};
+} // namespace Kleo
+#endif // KEYREARRANGECOLUMNSPROXYMODEL_H
diff --git a/src/models/modeltest.cpp b/src/models/modeltest.cpp
new file mode 100644
index 0000000..9e16dad
--- /dev/null
+++ b/src/models/modeltest.cpp
@@ -0,0 +1,530 @@
+/****************************************************************************
+**
+** Copyright (C) 2007 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Concurrent project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "modeltest.h"
+
+#include <QSize>
+
+Q_DECLARE_METATYPE(QModelIndex)
+
+/*!
+ Connect to all of the models signals. Whenever anything happens recheck everything.
+*/
+ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false)
+{
+ Q_ASSERT(model);
+
+ connect(model, &QAbstractItemModel::columnsAboutToBeInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::columnsAboutToBeRemoved, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::columnsInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::columnsRemoved, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::dataChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::headerDataChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::layoutChanged, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::modelReset, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::runAllTests);
+ connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::runAllTests);
+
+ // Special checks for inserting/removing
+ connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &ModelTest::layoutAboutToBeChanged);
+ connect(model, &QAbstractItemModel::layoutChanged, this, &ModelTest::layoutChanged);
+
+ connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::rowsAboutToBeInserted);
+ connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::rowsAboutToBeRemoved);
+ connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::rowsInserted);
+ connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::rowsRemoved);
+
+ runAllTests();
+}
+
+void ModelTest::runAllTests()
+{
+ if (fetchingMore) {
+ return;
+ }
+ nonDestructiveBasicTest();
+ rowCount();
+ columnCount();
+ hasIndex();
+ index();
+ parent();
+ data();
+}
+
+/*!
+ nonDestructiveBasicTest tries to call a number of the basic functions (not all)
+ to make sure the model doesn't outright segfault, testing the functions that makes sense.
+*/
+void ModelTest::nonDestructiveBasicTest()
+{
+ Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
+ model->canFetchMore(QModelIndex());
+ Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
+ Q_ASSERT(model->data(QModelIndex()) == QVariant());
+ fetchingMore = true;
+ model->fetchMore(QModelIndex());
+ fetchingMore = false;
+ Qt::ItemFlags flags = model->flags(QModelIndex());
+ Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
+ model->hasChildren(QModelIndex());
+ model->hasIndex(0, 0);
+ model->headerData(0, Qt::Horizontal);
+ model->index(0, 0);
+ model->itemData(QModelIndex());
+ QVariant cache;
+ model->match(QModelIndex(), -1, cache);
+ model->mimeTypes();
+ Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
+ Q_ASSERT(model->rowCount() >= 0);
+ QVariant variant;
+ model->setData(QModelIndex(), variant, -1);
+ model->setHeaderData(-1, Qt::Horizontal, QVariant());
+ model->setHeaderData(0, Qt::Horizontal, QVariant());
+ model->setHeaderData(999999, Qt::Horizontal, QVariant());
+ QMap<int, QVariant> roles;
+ model->sibling(0, 0, QModelIndex());
+ model->span(QModelIndex());
+ model->supportedDropActions();
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
+
+ Models that are dynamically populated are not as fully tested here.
+ */
+void ModelTest::rowCount()
+{
+ // check top row
+ QModelIndex topIndex = model->index(0, 0, QModelIndex());
+ int rows = model->rowCount(topIndex);
+ Q_ASSERT(rows >= 0);
+ if (rows > 0) {
+ Q_ASSERT(model->hasChildren(topIndex) == true);
+ }
+
+ QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
+ if (secondLevelIndex.isValid()) { // not the top level
+ // check a row count where parent is valid
+ rows = model->rowCount(secondLevelIndex);
+ Q_ASSERT(rows >= 0);
+ if (rows > 0) {
+ Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
+ }
+ }
+
+ // The models rowCount() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
+ */
+void ModelTest::columnCount()
+{
+ // check top row
+ QModelIndex topIndex = model->index(0, 0, QModelIndex());
+ Q_ASSERT(model->columnCount(topIndex) >= 0);
+
+ // check a column count where parent is valid
+ QModelIndex childIndex = model->index(0, 0, topIndex);
+ if (childIndex.isValid()) {
+ Q_ASSERT(model->columnCount(childIndex) >= 0);
+ }
+
+ // columnCount() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::hasIndex()
+ */
+void ModelTest::hasIndex()
+{
+ // Make sure that invalid values returns an invalid index
+ Q_ASSERT(model->hasIndex(-2, -2) == false);
+ Q_ASSERT(model->hasIndex(-2, 0) == false);
+ Q_ASSERT(model->hasIndex(0, -2) == false);
+
+ int rows = model->rowCount();
+ int columns = model->columnCount();
+
+ // check out of bounds
+ Q_ASSERT(model->hasIndex(rows, columns) == false);
+ Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
+
+ if (rows > 0) {
+ Q_ASSERT(model->hasIndex(0, 0) == true);
+ }
+
+ // hasIndex() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::index()
+ */
+void ModelTest::index()
+{
+ // Make sure that invalid values returns an invalid index
+ Q_ASSERT(model->index(-2, -2) == QModelIndex());
+ Q_ASSERT(model->index(-2, 0) == QModelIndex());
+ Q_ASSERT(model->index(0, -2) == QModelIndex());
+
+ int rows = model->rowCount();
+ int columns = model->columnCount();
+
+ if (rows == 0) {
+ return;
+ }
+
+ // Catch off by one errors
+ Q_ASSERT(model->index(rows, columns) == QModelIndex());
+ Q_ASSERT(model->index(0, 0).isValid() == true);
+
+ // Make sure that the same index is *always* returned
+ QModelIndex a = model->index(0, 0);
+ QModelIndex b = model->index(0, 0);
+ Q_ASSERT(a == b);
+
+ // index() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::parent()
+ */
+void ModelTest::parent()
+{
+ // Make sure the model wont crash and will return an invalid QModelIndex
+ // when asked for the parent of an invalid index.
+ Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
+
+ if (model->rowCount() == 0) {
+ return;
+ }
+
+ // Column 0 | Column 1 |
+ // QModelIndex() | |
+ // \- topIndex | topIndex1 |
+ // \- childIndex | childIndex1 |
+
+ // Common error test #1, make sure that a top level index has a parent
+ // that is a invalid QModelIndex.
+ QModelIndex topIndex = model->index(0, 0, QModelIndex());
+ Q_ASSERT(model->parent(topIndex) == QModelIndex());
+
+ // Common error test #2, make sure that a second level index has a parent
+ // that is the first level index.
+ if (model->rowCount(topIndex) > 0) {
+ QModelIndex childIndex = model->index(0, 0, topIndex);
+ Q_ASSERT(model->parent(childIndex) == topIndex);
+ }
+
+ // Common error test #3, the second column should NOT have the same children
+ // as the first column in a row.
+ // Usually the second column shouldn't have children.
+ QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
+ if (model->rowCount(topIndex1) > 0) {
+ QModelIndex childIndex = model->index(0, 0, topIndex);
+ QModelIndex childIndex1 = model->index(0, 0, topIndex1);
+ Q_ASSERT(childIndex != childIndex1);
+ }
+
+ // Full test, walk n levels deep through the model making sure that all
+ // parent's children correctly specify their parent.
+ checkChildren(QModelIndex());
+}
+
+/*!
+ Called from the parent() test.
+
+ A model that returns an index of parent X should also return X when asking
+ for the parent of the index.
+
+ This recursive function does pretty extensive testing on the whole model in an
+ effort to catch edge cases.
+
+ This function assumes that rowCount(), columnCount() and index() already work.
+ If they have a bug it will point it out, but the above tests should have already
+ found the basic bugs because it is easier to figure out the problem in
+ those tests then this one.
+ */
+void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth)
+{
+ // First just try walking back up the tree.
+ QModelIndex p = parent;
+ while (p.isValid()) {
+ p = p.parent();
+ }
+
+ // For models that are dynamically populated
+ if (model->canFetchMore(parent)) {
+ fetchingMore = true;
+ model->fetchMore(parent);
+ fetchingMore = false;
+ }
+
+ int rows = model->rowCount(parent);
+ int columns = model->columnCount(parent);
+
+ if (rows > 0) {
+ Q_ASSERT(model->hasChildren(parent));
+ }
+
+ // Some further testing against rows(), columns(), and hasChildren()
+ Q_ASSERT(rows >= 0);
+ Q_ASSERT(columns >= 0);
+ if (rows > 0) {
+ Q_ASSERT(model->hasChildren(parent) == true);
+ }
+
+ //qCDebug(KLEOPATRA_LOG) << "parent:" << model->data(parent).toString() << "rows:" << rows
+ // << "columns:" << columns << "parent column:" << parent.column();
+
+ Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
+ for (int r = 0; r < rows; ++r) {
+ if (model->canFetchMore(parent)) {
+ fetchingMore = true;
+ model->fetchMore(parent);
+ fetchingMore = false;
+ }
+ Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
+ for (int c = 0; c < columns; ++c) {
+ Q_ASSERT(model->hasIndex(r, c, parent) == true);
+ QModelIndex index = model->index(r, c, parent);
+ // rowCount() and columnCount() said that it existed...
+ Q_ASSERT(index.isValid() == true);
+
+ // index() should always return the same index when called twice in a row
+ QModelIndex modifiedIndex = model->index(r, c, parent);
+ Q_ASSERT(index == modifiedIndex);
+
+ // Make sure we get the same index if we request it twice in a row
+ QModelIndex a = model->index(r, c, parent);
+ QModelIndex b = model->index(r, c, parent);
+ Q_ASSERT(a == b);
+
+ // Some basic checking on the index that is returned
+ Q_ASSERT(index.model() == model);
+ Q_ASSERT(index.row() == r);
+ Q_ASSERT(index.column() == c);
+ // While you can technically return a QVariant usually this is a sign
+ // of an bug in data() Disable if this really is ok in your model.
+ Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
+
+ // If the next test fails here is some somewhat useful debug you play with.
+ /*
+ if (model->parent(index) != parent) {
+ qCDebug(KLEOPATRA_LOG) << r << c << currentDepth << model->data(index).toString()
+ << model->data(parent).toString();
+ qCDebug(KLEOPATRA_LOG) << index << parent << model->parent(index);
+ // And a view that you can even use to show the model.
+ //QTreeView view;
+ //view.setModel(model);
+ //view.show();
+ }*/
+
+ // Check that we can get back our real parent.
+ Q_ASSERT(model->parent(index) == parent);
+
+ // recursively go down the children
+ if (model->hasChildren(index) && currentDepth < 10) {
+ //qCDebug(KLEOPATRA_LOG) << r << c << "has children" << model->rowCount(index);
+ checkChildren(index, ++currentDepth);
+ }/* else { if (currentDepth >= 10) qCDebug(KLEOPATRA_LOG) << "checked 10 deep"; };*/
+
+ // make sure that after testing the children that the index doesn't change.
+ QModelIndex newerIndex = model->index(r, c, parent);
+ Q_ASSERT(index == newerIndex);
+ }
+ }
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::data()
+ */
+void ModelTest::data()
+{
+ // Invalid index should return an invalid qvariant
+ Q_ASSERT(!model->data(QModelIndex()).isValid());
+
+ if (model->rowCount() == 0) {
+ return;
+ }
+
+ // A valid index should have a valid QVariant data
+ Q_ASSERT(model->index(0, 0).isValid());
+
+ // shouldn't be able to set data on an invalid index
+ Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false);
+
+ // General Purpose roles that should return a QString
+ QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QString>(variant));
+ }
+ variant = model->data(model->index(0, 0), Qt::StatusTipRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QString>(variant));
+ }
+ variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QString>(variant));
+ }
+
+ // General Purpose roles that should return a QSize
+ variant = model->data(model->index(0, 0), Qt::SizeHintRole);
+ if (variant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QSize>(variant));
+ }
+
+ // General Purpose roles that should return a QFont
+ QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
+ if (fontVariant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QFont>(fontVariant));
+ }
+
+ // Check that the alignment is one we know about
+ QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
+ if (textAlignmentVariant.isValid()) {
+ int alignment = textAlignmentVariant.toInt();
+ Q_ASSERT(alignment == Qt::AlignLeft ||
+ alignment == Qt::AlignRight ||
+ alignment == Qt::AlignHCenter ||
+ alignment == Qt::AlignJustify ||
+ alignment == Qt::AlignTop ||
+ alignment == Qt::AlignBottom ||
+ alignment == Qt::AlignVCenter ||
+ alignment == Qt::AlignCenter ||
+ alignment == Qt::AlignAbsolute ||
+ alignment == Qt::AlignLeading ||
+ alignment == Qt::AlignTrailing);
+ }
+
+ // General Purpose roles that should return a QColor
+ QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
+ if (colorVariant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
+ }
+
+ colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
+ if (colorVariant.isValid()) {
+ Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
+ }
+
+ // Check that the "check state" is one we know about.
+ QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
+ if (checkStateVariant.isValid()) {
+ int state = checkStateVariant.toInt();
+ Q_ASSERT(state == Qt::Unchecked ||
+ state == Qt::PartiallyChecked ||
+ state == Qt::Checked);
+ }
+}
+
+/*!
+ Store what is about to be inserted to make sure it actually happens
+
+ \sa rowsInserted()
+ */
+void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
+{
+ Q_UNUSED(end);
+ Changing c;
+ c.parent = parent;
+ c.oldSize = model->rowCount(parent);
+ c.last = model->data(model->index(start - 1, 0, parent));
+ c.next = model->data(model->index(start, 0, parent));
+ insert.push(c);
+}
+
+/*!
+ Confirm that what was said was going to happen actually did
+
+ \sa rowsAboutToBeInserted()
+ */
+void ModelTest::rowsInserted(const QModelIndex &parent, int start, int end)
+{
+ Changing c = insert.pop();
+ Q_ASSERT(c.parent == parent);
+ Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
+ Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
+ /*
+ if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
+ qCDebug(KLEOPATRA_LOG) << start << end;
+ for (int i=0; i < model->rowCount(); ++i)
+ qCDebug(KLEOPATRA_LOG) << model->index(i, 0).data().toString();
+ qCDebug(KLEOPATRA_LOG) << c.next << model->data(model->index(end + 1, 0, c.parent));
+ }
+ */
+ Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
+}
+
+void ModelTest::layoutAboutToBeChanged()
+{
+ for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) {
+ changing.append(QPersistentModelIndex(model->index(i, 0)));
+ }
+}
+
+void ModelTest::layoutChanged()
+{
+ for (int i = 0; i < changing.count(); ++i) {
+ QPersistentModelIndex p = changing[i];
+ Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
+ }
+ changing.clear();
+}
+
+/*!
+ Store what is about to be inserted to make sure it actually happens
+
+ \sa rowsRemoved()
+ */
+void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
+{
+ Changing c;
+ c.parent = parent;
+ c.oldSize = model->rowCount(parent);
+ c.last = model->data(model->index(start - 1, 0, parent));
+ c.next = model->data(model->index(end + 1, 0, parent));
+ remove.push(c);
+}
+
+/*!
+ Confirm that what was said was going to happen actually did
+
+ \sa rowsAboutToBeRemoved()
+ */
+void ModelTest::rowsRemoved(const QModelIndex &parent, int start, int end)
+{
+ Changing c = remove.pop();
+ Q_ASSERT(c.parent == parent);
+ Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
+ Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
+ Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
+}
+
diff --git a/src/models/modeltest.h b/src/models/modeltest.h
new file mode 100644
index 0000000..215a9e7
--- /dev/null
+++ b/src/models/modeltest.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2007 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Concurrent project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef MODELTEST_H
+#define MODELTEST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QStack>
+
+class ModelTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ ModelTest(QAbstractItemModel *model, QObject *parent = Q_NULLPTR);
+
+private Q_SLOTS:
+ void nonDestructiveBasicTest();
+ void rowCount();
+ void columnCount();
+ void hasIndex();
+ void index();
+ void parent();
+ void data();
+
+protected Q_SLOTS:
+ void runAllTests();
+ void layoutAboutToBeChanged();
+ void layoutChanged();
+ void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
+ void rowsInserted(const QModelIndex &parent, int start, int end);
+ void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
+ void rowsRemoved(const QModelIndex &parent, int start, int end);
+
+private:
+ void checkChildren(const QModelIndex &parent, int currentDepth = 0);
+
+ QAbstractItemModel *model;
+
+ struct Changing {
+ QModelIndex parent;
+ int oldSize;
+ QVariant last;
+ QVariant next;
+ };
+ QStack<Changing> insert;
+ QStack<Changing> remove;
+
+ bool fetchingMore;
+
+ QList<QPersistentModelIndex> changing;
+};
+
+#endif
diff --git a/src/models/subkeylistmodel.cpp b/src/models/subkeylistmodel.cpp
new file mode 100644
index 0000000..024e9eb
--- /dev/null
+++ b/src/models/subkeylistmodel.cpp
@@ -0,0 +1,221 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/subkeylistmodel.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "subkeylistmodel.h"
+#include "utils/formatting.h"
+
+#include <gpgme++/key.h>
+
+#include <KLocalizedString>
+
+#include <QVariant>
+#include <QDate>
+
+#include <boost/bind.hpp>
+
+#include <algorithm>
+#include <iterator>
+
+using namespace GpgME;
+using namespace Kleo;
+using namespace boost;
+
+class SubkeyListModel::Private
+{
+ friend class ::Kleo::SubkeyListModel;
+ SubkeyListModel *const q;
+public:
+ explicit Private(SubkeyListModel *qq)
+ : q(qq), key() {}
+
+private:
+ Key key;
+};
+
+SubkeyListModel::SubkeyListModel(QObject *p)
+ : QAbstractTableModel(p), d(new Private(this))
+{
+
+}
+
+SubkeyListModel::~SubkeyListModel() {}
+
+Key SubkeyListModel::key() const
+{
+ return d->key;
+}
+
+// slot
+void SubkeyListModel::setKey(const Key &key)
+{
+
+ const Key oldKey = d->key;
+
+
+ if (qstricmp(key.primaryFingerprint(), oldKey.primaryFingerprint()) != 0) {
+ // different key -> reset
+ beginResetModel();
+ d->key = key;
+ endResetModel();
+ return;
+ }
+
+ d->key = key;
+
+ // ### diff them, and signal more fine-grained than this:
+
+ if (key.numSubkeys() > 0 && oldKey.numSubkeys() == key.numSubkeys()) {
+ Q_EMIT dataChanged(index(0, 0), index(key.numSubkeys() - 1, NumColumns - 1));
+ } else {
+ Q_EMIT layoutAboutToBeChanged();
+ Q_EMIT layoutChanged();
+ }
+}
+
+Subkey SubkeyListModel::subkey(const QModelIndex &idx) const
+{
+ if (idx.isValid()) {
+ return d->key.subkey(idx.row());
+ } else {
+ return Subkey();
+ }
+}
+
+std::vector<Subkey> SubkeyListModel::subkeys(const QList<QModelIndex> &indexes) const
+{
+ std::vector<Subkey> result;
+ result.reserve(indexes.size());
+ std::transform(indexes.begin(), indexes.end(),
+ std::back_inserter(result),
+ boost::bind(&SubkeyListModel::subkey, this, _1));
+ return result;
+}
+
+QModelIndex SubkeyListModel::index(const Subkey &subkey, int col) const
+{
+ // O(N), but not sorted, so no better way...
+ for (unsigned int row = 0, end = d->key.numSubkeys(); row != end; ++row)
+ if (qstricmp(subkey.keyID(), d->key.subkey(row).keyID()) == 0) {
+ return index(row, col);
+ }
+ return QModelIndex();
+}
+
+QList<QModelIndex> SubkeyListModel::indexes(const std::vector<Subkey> &subkeys) const
+{
+ QList<QModelIndex> result;
+ // O(N*M), but who cares...?
+ std::transform(subkeys.begin(), subkeys.end(),
+ std::back_inserter(result),
+ // if some compilers are complaining about ambiguous overloads, use this line instead:
+ //bind( static_cast<QModelIndex(SubKeyListModel::*)(const Subkey&,int)const>( &SubkeyListModel::index ), this, _1, 0 ) );
+ boost::bind(&SubkeyListModel::index, this, _1, 0));
+ return result;
+}
+
+void SubkeyListModel::clear()
+{
+ beginResetModel();
+ d->key = Key::null;
+ endResetModel();
+}
+
+int SubkeyListModel::columnCount(const QModelIndex &) const
+{
+ return NumColumns;
+}
+
+int SubkeyListModel::rowCount(const QModelIndex &pidx) const
+{
+ return pidx.isValid() ? 0 : d->key.numSubkeys();
+}
+
+QVariant SubkeyListModel::headerData(int section, Qt::Orientation o, int role) const
+{
+ if (o == Qt::Horizontal)
+ if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole)
+ switch (section) {
+ case ID: return i18n("ID");
+ case Type: return i18n("Type");
+ case ValidFrom: return i18n("Valid From");
+ case ValidUntil: return i18n("Valid Until");
+ case Status: return i18n("Status");
+ case Strength: return i18n("Strength");
+ case Usage: return i18n("Usage");
+ case NumColumns:;
+ }
+ return QVariant();
+}
+
+QVariant SubkeyListModel::data(const QModelIndex &idx, int role) const
+{
+
+ if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole) {
+ return QVariant();
+ }
+
+ const Subkey subkey = this->subkey(idx);
+ if (subkey.isNull()) {
+ return QVariant();
+ }
+
+ switch (idx.column()) {
+ case ID:
+ return QString::fromLatin1(subkey.keyID());
+ case Type:
+ return Formatting::type(subkey);
+ case ValidFrom:
+ if (role == Qt::EditRole) {
+ return Formatting::creationDate(subkey);
+ } else {
+ return Formatting::creationDateString(subkey);
+ }
+ case ValidUntil:
+ if (role == Qt::EditRole) {
+ return Formatting::expirationDate(subkey);
+ } else {
+ return Formatting::expirationDateString(subkey);
+ }
+ case Status:
+ return Formatting::validityShort(subkey);
+ case Usage:
+ return Formatting::usageString(subkey);
+ case Strength:
+ const QString algName = QString::fromStdString(subkey.algoName());
+ // For ECC keys the algo name is something like bp512 and directly
+ // indicated the "strength"
+ return algName.isEmpty() ? QVariant(subkey.length()) : algName;
+ }
+
+ return QVariant();
+}
+
diff --git a/src/models/subkeylistmodel.h b/src/models/subkeylistmodel.h
new file mode 100644
index 0000000..7da9297
--- /dev/null
+++ b/src/models/subkeylistmodel.h
@@ -0,0 +1,96 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/subkeylistmodel.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef __KLEOPATRA_MODELS_SUBKEYLISTMODEL_H__
+#define __KLEOPATRA_MODELS_SUBKEYLISTMODEL_H__
+
+#include <QAbstractTableModel>
+
+#include <kleo_export.h>
+
+#include <vector>
+
+namespace GpgME
+{
+class Key;
+class Subkey;
+}
+
+namespace Kleo
+{
+
+class KLEO_EXPORT SubkeyListModel : public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ explicit SubkeyListModel(QObject *parent = Q_NULLPTR);
+ ~SubkeyListModel();
+
+ GpgME::Key key() const;
+
+ enum Columns {
+ ID,
+ Type,
+ ValidFrom,
+ ValidUntil,
+ Status,
+ Strength,
+ Usage,
+
+ NumColumns,
+ Icon = ID // which column shall the icon be displayed in?
+ };
+
+ GpgME::Subkey subkey(const QModelIndex &idx) const;
+ std::vector<GpgME::Subkey> subkeys(const QList<QModelIndex> &indexes) const;
+
+ using QAbstractTableModel::index;
+ QModelIndex index(const GpgME::Subkey &subkey, int col = 0) const;
+ QList<QModelIndex> indexes(const std::vector<GpgME::Subkey> &subkeys) const;
+
+public Q_SLOTS:
+ void setKey(const GpgME::Key &key);
+ void clear();
+
+public:
+ int columnCount(const QModelIndex &pidx = QModelIndex()) const Q_DECL_OVERRIDE;
+ int rowCount(const QModelIndex &pidx = QModelIndex()) const Q_DECL_OVERRIDE;
+ QVariant headerData(int section, Qt::Orientation o, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+
+private:
+ class Private;
+ QScopedPointer<Private> const d;
+};
+
+}
+
+#endif /* __KLEOPATRA_MODELS_SUBKEYLISTMODEL_H__ */
diff --git a/src/models/useridlistmodel.cpp b/src/models/useridlistmodel.cpp
new file mode 100644
index 0000000..36364db
--- /dev/null
+++ b/src/models/useridlistmodel.cpp
@@ -0,0 +1,309 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/useridlistmodel.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 Klarälvdalens Datakonsult AB
+ 2016 Andre Heinecke <aheinecke@gnupg.org>
+
+ 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "useridlistmodel.h"
+#include "utils/formatting.h"
+
+#include <gpgme++/key.h>
+
+#include <KLocalizedString>
+
+#include <QVariant>
+#include <QDate>
+
+using namespace GpgME;
+using namespace Kleo;
+
+class UIDModelItem
+{
+ // A uid model item can either be a UserID::Signature or a UserID.
+ // you can find out which it is if the uid or the signature return
+ // null values. (Not null but isNull)
+ //
+public:
+ explicit UIDModelItem(const UserID::Signature &sig, UIDModelItem *parentItem)
+ {
+ mItemData << QString::fromUtf8(sig.signerKeyID())
+ << Formatting::prettyName(sig)
+ << Formatting::prettyEMail(sig)
+ << Formatting::creationDateString(sig)
+ << Formatting::expirationDateString(sig)
+ << Formatting::validityShort(sig);
+ mSig = sig;
+ mParentItem = parentItem;
+ }
+
+ explicit UIDModelItem(const UserID &uid, UIDModelItem *parentItem)
+ {
+ mItemData << Formatting::prettyUserID(uid);
+ mUid = uid;
+ mParentItem = parentItem;
+ }
+
+ // The root item
+ explicit UIDModelItem() : mParentItem(0)
+ {
+ mItemData << i18n("ID")
+ << i18n("Name")
+ << i18n("EMail")
+ << i18n("Valid From")
+ << i18n("Valid Until")
+ << i18n("Status");
+ }
+
+ ~UIDModelItem()
+ {
+ qDeleteAll(mChildItems);
+ }
+
+ void appendChild(UIDModelItem *child)
+ {
+ mChildItems << child;
+ }
+
+ UIDModelItem *child(int row) const
+ {
+ return mChildItems.value(row);
+ }
+
+ const UIDModelItem *constChild(int row) const
+ {
+ return mChildItems.value(row);
+ }
+
+ int childCount() const
+ {
+ return mChildItems.count();
+ }
+
+ int columnCount() const
+ {
+ if (childCount()) {
+ // We take the value from the first child
+ // as we are likely a UID and our children
+ // are UID Signatures.
+ return constChild(0)->columnCount();
+ }
+ return mItemData.count();
+ }
+
+ QVariant data(int column) const
+ {
+ return mItemData.value(column);
+ }
+
+ int row() const
+ {
+ if (mParentItem) {
+ return mParentItem->mChildItems.indexOf(const_cast<UIDModelItem*>(this));
+ }
+ return 0;
+ }
+
+ UIDModelItem *parentItem() const
+ {
+ return mParentItem;
+ }
+
+ UserID::Signature signature() const
+ {
+ return mSig;
+ }
+
+ UserID uid() const
+ {
+ return mUid;
+ }
+
+private:
+ QList<UIDModelItem*> mChildItems;
+ QList<QVariant> mItemData;
+ UIDModelItem *mParentItem;
+ UserID::Signature mSig;
+ UserID mUid;
+};
+
+UserIDListModel::UserIDListModel(QObject *p)
+ : QAbstractItemModel(p), mRootItem(0)
+{
+}
+
+UserIDListModel::~UserIDListModel()
+{
+ delete mRootItem;
+}
+
+Key UserIDListModel::key() const
+{
+ return mKey;
+}
+
+void UserIDListModel::setKey(const Key &key)
+{
+ beginResetModel();
+ delete mRootItem;
+ mKey = key;
+
+ mRootItem = new UIDModelItem();
+ for (int i = 0, ids = key.numUserIDs(); i < ids; ++i) {
+ UserID uid = key.userID(i);
+ UIDModelItem *uidItem = new UIDModelItem(uid, mRootItem);
+ mRootItem->appendChild(uidItem);
+ for (int j = 0, sigs = uid.numSignatures(); j < sigs; ++j) {
+ UserID::Signature sig = uid.signature(j);
+ UIDModelItem *sigItem = new UIDModelItem(sig, uidItem);
+ uidItem->appendChild(sigItem);
+ }
+ }
+
+ endResetModel();
+}
+
+int UserIDListModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return static_cast<UIDModelItem*>(parent.internalPointer())->columnCount();
+ }
+
+ if (!mRootItem) {
+ return 0;
+ }
+
+ return mRootItem->columnCount();
+}
+
+int UserIDListModel::rowCount(const QModelIndex &parent) const
+{
+ UIDModelItem *parentItem;
+ if (parent.column() > 0 || !mRootItem) {
+ return 0;
+ }
+
+ if (!parent.isValid()) {
+ parentItem = mRootItem;
+ } else {
+ parentItem = static_cast<UIDModelItem*>(parent.internalPointer());
+ }
+
+ return parentItem->childCount();
+}
+
+QModelIndex UserIDListModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (!hasIndex(row, column, parent)) {
+ return QModelIndex();
+ }
+
+ UIDModelItem *parentItem;
+
+ if (!parent.isValid()) {
+ parentItem = mRootItem;
+ } else {
+ parentItem = static_cast<UIDModelItem*>(parent.internalPointer());
+ }
+
+ UIDModelItem *childItem = parentItem->child(row);
+ if (childItem) {
+ return createIndex(row, column, childItem);
+ } else {
+ return QModelIndex();
+ }
+}
+
+QModelIndex UserIDListModel::parent(const QModelIndex &index) const
+{
+ if (!index.isValid()) {
+ return QModelIndex();
+ }
+ UIDModelItem *childItem = static_cast<UIDModelItem*>(index.internalPointer());
+ UIDModelItem *parentItem = childItem->parentItem();
+
+ if (parentItem == mRootItem) {
+ return QModelIndex();
+ }
+
+ return createIndex(parentItem->row(), 0, parentItem);
+}
+
+QVariant UserIDListModel::headerData(int section, Qt::Orientation o, int role) const
+{
+ if (o == Qt::Horizontal && mRootItem) {
+ if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) {
+ return mRootItem->data(section);
+ }
+ }
+ return QVariant();
+}
+
+QVariant UserIDListModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid()) {
+ return QVariant();
+ }
+
+ if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole) {
+ return QVariant();
+ }
+
+ UIDModelItem *item = static_cast<UIDModelItem*>(index.internalPointer());
+
+ return item->data(index.column());
+}
+
+QVector<UserID> UserIDListModel::userIDs (const QModelIndexList &indexs) const {
+ QVector<GpgME::UserID> ret;
+ Q_FOREACH (const QModelIndex &idx, indexs) {
+ if (!idx.isValid()) {
+ continue;
+ }
+ UIDModelItem *item = static_cast<UIDModelItem*>(idx.internalPointer());
+ if (!item->uid().isNull()) {
+ ret << item->uid();
+ }
+ }
+ return ret;
+}
+
+QVector<UserID::Signature> UserIDListModel::signatures (const QModelIndexList &indexs) const {
+ QVector<GpgME::UserID::Signature> ret;
+ Q_FOREACH (const QModelIndex &idx, indexs) {
+ if (!idx.isValid()) {
+ continue;
+ }
+ UIDModelItem *item = static_cast<UIDModelItem*>(idx.internalPointer());
+ if (!item->signature().isNull()) {
+ ret << item->signature();
+ }
+ }
+ return ret;
+}
diff --git a/src/models/useridlistmodel.h b/src/models/useridlistmodel.h
new file mode 100644
index 0000000..eacd222
--- /dev/null
+++ b/src/models/useridlistmodel.h
@@ -0,0 +1,79 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ models/userIDlistmodel.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 Klarälvdalens Datakonsult AB
+ 2016 Andre Heinecke <aheinecke@gnupg.org>
+
+ 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef __KLEOPATRA_MODELS_USERIDLISTMODEL_H__
+#define __KLEOPATRA_MODELS_USERIDLISTMODEL_H__
+
+#include <QAbstractItemModel>
+
+#include <kleo_export.h>
+
+#include <gpgme++/key.h> // since Signature is nested in UserID...
+
+class UIDModelItem;
+
+namespace Kleo
+{
+
+class KLEO_EXPORT UserIDListModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ explicit UserIDListModel(QObject *parent = Q_NULLPTR);
+ ~UserIDListModel();
+
+ GpgME::Key key() const;
+
+public:
+ QVector<GpgME::UserID> userIDs(const QModelIndexList &indexs) const;
+ QVector<GpgME::UserID::Signature> signatures(const QModelIndexList &indexs) const;
+
+public Q_SLOTS:
+ void setKey(const GpgME::Key &key);
+
+public:
+ int columnCount(const QModelIndex &pindex = QModelIndex()) const Q_DECL_OVERRIDE;
+ int rowCount(const QModelIndex &pindex = QModelIndex()) const Q_DECL_OVERRIDE;
+ QVariant headerData(int section, Qt::Orientation o, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+
+ QModelIndex index(int row, int col, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
+
+private:
+ GpgME::Key mKey;
+ UIDModelItem *mRootItem;
+};
+
+}
+
+#endif /* __KLEOPATRA_MODELS_USERIDLISTMODEL_H__ */
diff --git a/src/utils/filesystemwatcher.cpp b/src/utils/filesystemwatcher.cpp
new file mode 100644
index 0000000..cbee718
--- /dev/null
+++ b/src/utils/filesystemwatcher.cpp
@@ -0,0 +1,330 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ filesystemwatcher.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2008 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "filesystemwatcher.h"
+#include "kleo/stl_util.h"
+
+#include <libkleo_debug.h>
+
+#include <QFileSystemWatcher>
+#include <QString>
+#include <QStringList>
+#include <QTimer>
+#include <QDir>
+
+#include <boost/bind.hpp>
+
+#include <set>
+#include <cassert>
+
+using namespace Kleo;
+using namespace boost;
+
+class FileSystemWatcher::Private
+{
+ FileSystemWatcher *const q;
+public:
+ explicit Private(FileSystemWatcher *qq, const QStringList &paths = QStringList());
+ ~Private()
+ {
+ delete m_watcher;
+ }
+
+ void onFileChanged(const QString &path);
+ void onDirectoryChanged(const QString &path);
+ void handleTimer();
+ void onTimeout();
+
+ void connectWatcher();
+
+ QFileSystemWatcher *m_watcher;
+ QTimer m_timer;
+ std::set<QString> m_seenPaths;
+ std::set<QString> m_cachedDirectories;
+ std::set<QString> m_cachedFiles;
+ QStringList m_paths, m_blacklist, m_whitelist;
+};
+
+FileSystemWatcher::Private::Private(FileSystemWatcher *qq, const QStringList &paths)
+ : q(qq),
+ m_watcher(0),
+ m_paths(paths)
+{
+ m_timer.setSingleShot(true);
+ connect(&m_timer, SIGNAL(timeout()), q, SLOT(onTimeout()));
+}
+
+static bool is_matching(const QString &file, const QStringList &list)
+{
+ Q_FOREACH (const QString &entry, list)
+ if (QRegExp(entry, Qt::CaseInsensitive, QRegExp::Wildcard).exactMatch(file)) {
+ return true;
+ }
+ return false;
+}
+
+static bool is_blacklisted(const QString &file, const QStringList &blacklist)
+{
+ return is_matching(file, blacklist);
+}
+
+static bool is_whitelisted(const QString &file, const QStringList &whitelist)
+{
+ if (whitelist.empty()) {
+ return true; // special case
+ }
+ return is_matching(file, whitelist);
+}
+
+void FileSystemWatcher::Private::onFileChanged(const QString &path)
+{
+ const QFileInfo fi(path);
+ if (is_blacklisted(fi.fileName(), m_blacklist)) {
+ return;
+ }
+ if (!is_whitelisted(fi.fileName(), m_whitelist)) {
+ return;
+ }
+ qCDebug(LIBKLEO_LOG) << path;
+ m_seenPaths.insert(path);
+ m_cachedFiles.insert(path);
+ handleTimer();
+}
+
+static QStringList list_dir_absolute(const QString &path, const QStringList &blacklist, const QStringList &whitelist)
+{
+ QDir dir(path);
+ QStringList entries = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
+ QStringList::iterator end =
+ std::remove_if(entries.begin(), entries.end(),
+ boost::bind(is_blacklisted, _1, cref(blacklist)));
+ if (!whitelist.empty())
+ end = std::remove_if(entries.begin(), end,
+ !boost::bind(is_whitelisted, _1, cref(whitelist)));
+ entries.erase(end, entries.end());
+ kdtools::sort(entries);
+
+ std::transform(entries.begin(), entries.end(), entries.begin(),
+ boost::bind(&QDir::absoluteFilePath, &dir, _1));
+
+ return entries;
+}
+
+static QStringList find_new_files(const QStringList &current, const std::set<QString> &seen)
+{
+ QStringList result;
+ std::set_difference(current.begin(), current.end(),
+ seen.begin(), seen.end(),
+ std::back_inserter(result));
+ return result;
+}
+
+void FileSystemWatcher::Private::onDirectoryChanged(const QString &path)
+{
+ const QStringList newFiles = find_new_files(list_dir_absolute(path, m_blacklist, m_whitelist), m_seenPaths);
+
+ if (newFiles.empty()) {
+ return;
+ }
+
+ qCDebug(LIBKLEO_LOG) << "newFiles" << newFiles;
+
+ m_cachedFiles.insert(newFiles.begin(), newFiles.end());
+ q->addPaths(newFiles);
+
+ m_cachedDirectories.insert(path);
+ handleTimer();
+}
+
+void FileSystemWatcher::Private::onTimeout()
+{
+ std::set<QString> dirs, files;
+
+ dirs.swap(m_cachedDirectories);
+ files.swap(m_cachedFiles);
+
+ if (dirs.empty() && files.empty()) {
+ return;
+ }
+
+ Q_EMIT q->triggered();
+
+ Q_FOREACH (const QString &i, dirs) {
+ Q_EMIT q->directoryChanged(i);
+ }
+ Q_FOREACH (const QString &i, files) {
+ Q_EMIT q->fileChanged(i);
+ }
+}
+
+void FileSystemWatcher::Private::handleTimer()
+{
+ if (m_timer.interval() == 0) {
+ onTimeout();
+ return;
+ }
+ m_timer.start();
+}
+
+void FileSystemWatcher::Private::connectWatcher()
+{
+ if (!m_watcher) {
+ return;
+ }
+ connect(m_watcher, SIGNAL(directoryChanged(QString)), q, SLOT(onDirectoryChanged(QString)));
+ connect(m_watcher, SIGNAL(fileChanged(QString)), q, SLOT(onFileChanged(QString)));
+}
+
+FileSystemWatcher::FileSystemWatcher(QObject *p)
+ : QObject(p), d(new Private(this))
+{
+ setEnabled(true);
+}
+
+FileSystemWatcher::FileSystemWatcher(const QStringList &paths, QObject *p)
+ : QObject(p), d(new Private(this, paths))
+{
+ setEnabled(true);
+}
+
+void FileSystemWatcher::setEnabled(bool enable)
+{
+ if (isEnabled() == enable) {
+ return;
+ }
+ if (enable) {
+ assert(!d->m_watcher);
+ d->m_watcher = new QFileSystemWatcher;
+ if (!d->m_paths.empty()) {
+ d->m_watcher->addPaths(d->m_paths);
+ }
+ d->connectWatcher();
+ } else {
+ assert(d->m_watcher);
+ delete d->m_watcher;
+ d->m_watcher = 0;
+ }
+}
+
+bool FileSystemWatcher::isEnabled() const
+{
+ return d->m_watcher != 0;
+}
+
+FileSystemWatcher::~FileSystemWatcher()
+{
+}
+
+void FileSystemWatcher::setDelay(int ms)
+{
+ assert(ms >= 0);
+ d->m_timer.setInterval(ms);
+}
+
+int FileSystemWatcher::delay() const
+{
+ return d->m_timer.interval();
+}
+
+void FileSystemWatcher::blacklistFiles(const QStringList &paths)
+{
+ d->m_blacklist += paths;
+ QStringList blacklisted;
+ d->m_paths.erase(kdtools::separate_if(d->m_paths.begin(), d->m_paths.end(),
+ std::back_inserter(blacklisted), d->m_paths.begin(),
+ boost::bind(is_blacklisted, _1, cref(d->m_blacklist))).second, d->m_paths.end());
+ if (d->m_watcher && !blacklisted.empty()) {
+ d->m_watcher->removePaths(blacklisted);
+ }
+}
+
+void FileSystemWatcher::whitelistFiles(const QStringList &patterns)
+{
+ d->m_whitelist += patterns;
+ // ### would be nice to add newly-matching paths here right away,
+ // ### but it's not as simple as blacklisting above, esp. since we
+ // ### don't want to subject addPath()'ed paths to whitelisting.
+}
+
+static QStringList resolve(const QStringList &paths, const QStringList &blacklist, const QStringList &whitelist)
+{
+ if (paths.empty()) {
+ return QStringList();
+ }
+ QStringList result;
+ Q_FOREACH (const QString &path, paths)
+ if (QDir(path).exists()) {
+ result += list_dir_absolute(path, blacklist, whitelist);
+ }
+ return result + resolve(result, blacklist, whitelist);
+}
+
+void FileSystemWatcher::addPaths(const QStringList &paths)
+{
+ if (paths.empty()) {
+ return;
+ }
+ const QStringList newPaths = paths + resolve(paths, d->m_blacklist, d->m_whitelist);
+ if (!newPaths.empty()) {
+ qCDebug(LIBKLEO_LOG) << "adding\n " << newPaths.join(QStringLiteral("\n ")) << "\n/end";
+ }
+ d->m_paths += newPaths;
+ d->m_seenPaths.insert(newPaths.begin(), newPaths.end());
+ if (d->m_watcher && !newPaths.empty()) {
+ d->m_watcher->addPaths(newPaths);
+ }
+}
+
+void FileSystemWatcher::addPath(const QString &path)
+{
+ addPaths(QStringList(path));
+}
+
+void FileSystemWatcher::removePaths(const QStringList &paths)
+{
+ if (paths.empty()) {
+ return;
+ }
+ Q_FOREACH (const QString &i, paths) {
+ d->m_paths.removeAll(i);
+ }
+ if (d->m_watcher) {
+ d->m_watcher->removePaths(paths);
+ }
+}
+
+void FileSystemWatcher::removePath(const QString &path)
+{
+ removePaths(QStringList(path));
+}
+
+#include "moc_filesystemwatcher.cpp"
diff --git a/src/utils/filesystemwatcher.h b/src/utils/filesystemwatcher.h
new file mode 100644
index 0000000..0db7329
--- /dev/null
+++ b/src/utils/filesystemwatcher.h
@@ -0,0 +1,85 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ filesystemwatcher.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2008 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KLEOPATRA_UTILS_FILESYSTEMWATCHER_H__
+#define __KLEOPATRA_UTILS_FILESYSTEMWATCHER_H__
+
+#include <QObject>
+
+#include <kleo_export.h>
+
+class QString;
+class QStringList;
+
+namespace Kleo
+{
+
+class KLEO_EXPORT FileSystemWatcher : public QObject
+{
+ Q_OBJECT
+public:
+ explicit FileSystemWatcher(QObject *parent = Q_NULLPTR);
+ explicit FileSystemWatcher(const QStringList &paths, QObject *parent = Q_NULLPTR);
+ ~FileSystemWatcher();
+
+ void setDelay(int ms);
+ int delay() const;
+
+ void setEnabled(bool enable);
+ bool isEnabled() const;
+
+ void addPaths(const QStringList &paths);
+ void addPath(const QString &path);
+
+ void blacklistFiles(const QStringList &patterns);
+ void whitelistFiles(const QStringList &patterns);
+
+ QStringList directories() const;
+ QStringList files() const;
+ void removePaths(const QStringList &path);
+ void removePath(const QString &path);
+
+Q_SIGNALS:
+ void directoryChanged(const QString &path);
+ void fileChanged(const QString &path);
+ void triggered();
+
+private:
+ class Private;
+ QScopedPointer<Private> const d;
+ Q_PRIVATE_SLOT(d, void onFileChanged(QString))
+ Q_PRIVATE_SLOT(d, void onDirectoryChanged(QString))
+ Q_PRIVATE_SLOT(d, void onTimeout())
+};
+}
+
+#endif // __KLEOPATRA_UTILS_FILESYSTEMWATCHER_H__
diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
new file mode 100644
index 0000000..dbd8a21
--- /dev/null
+++ b/src/utils/formatting.cpp
@@ -0,0 +1,791 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ utils/formatting.cpp
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "formatting.h"
+#include "kleo/dn.h"
+
+#include <gpgme++/key.h>
+#include <gpgme++/importresult.h>
+
+#include <KLocalizedString>
+#include <KEmailAddress>
+
+#include <QString>
+#include <QStringList>
+#include <QDateTime>
+#include <QTextDocument> // for Qt::escape
+#include <QLocale>
+#include <QIcon>
+
+using namespace GpgME;
+using namespace Kleo;
+
+//
+// Name
+//
+
+QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_)
+{
+
+ if (proto == OpenPGP) {
+ const QString name = QString::fromUtf8(name_);
+ if (name.isEmpty()) {
+ return QString();
+ }
+ const QString comment = QString::fromUtf8(comment_);
+ if (comment.isEmpty()) {
+ return name;
+ }
+ return QStringLiteral("%1 (%2)").arg(name, comment);
+ }
+
+ if (proto == CMS) {
+ const DN subject(id);
+ const QString cn = subject[QStringLiteral("CN")].trimmed();
+ if (cn.isEmpty()) {
+ return subject.prettyDN();
+ }
+ return cn;
+ }
+
+ return QString();
+}
+
+QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_)
+{
+ return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_));
+}
+
+QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment)
+{
+
+ if (proto == OpenPGP) {
+ if (name.isEmpty()) {
+ if (email.isEmpty()) {
+ return QString();
+ } else if (comment.isEmpty()) {
+ return QStringLiteral("<%1>").arg(email);
+ } else {
+ return QStringLiteral("(%2) <%1>").arg(email, comment);
+ }
+ }
+ if (email.isEmpty()) {
+ if (comment.isEmpty()) {
+ return name;
+ } else {
+ return QStringLiteral("%1 (%2)").arg(name, comment);
+ }
+ }
+ if (comment.isEmpty()) {
+ return QStringLiteral("%1 <%2>").arg(name, email);
+ } else {
+ return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
+ }
+ }
+
+ if (proto == CMS) {
+ const DN subject(id);
+ const QString cn = subject[QStringLiteral("CN")].trimmed();
+ if (cn.isEmpty()) {
+ return subject.prettyDN();
+ }
+ return cn;
+ }
+ return QString();
+}
+
+QString Formatting::prettyUserID(const UserID &uid)
+{
+ if (uid.parent().protocol() == OpenPGP) {
+ return prettyNameAndEMail(uid);
+ }
+ const QByteArray id = QByteArray(uid.id()).trimmed();
+ if (id.startsWith('<')) {
+ return prettyEMail(uid.email(), uid.id());
+ }
+ if (id.startsWith('('))
+ // ### parse uri/dns:
+ {
+ return QString::fromUtf8(uid.id());
+ } else {
+ return DN(uid.id()).prettyDN();
+ }
+}
+
+QString Formatting::prettyKeyID(const char *id)
+{
+ if (!id) {
+ return QString();
+ }
+ return QLatin1String("0x") + QString::fromLatin1(id).toUpper();
+}
+
+QString Formatting::prettyNameAndEMail(const UserID &uid)
+{
+ return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
+}
+
+QString Formatting::prettyNameAndEMail(const Key &key)
+{
+ return prettyNameAndEMail(key.userID(0));
+}
+
+QString Formatting::prettyName(const Key &key)
+{
+ return prettyName(key.userID(0));
+}
+
+QString Formatting::prettyName(const UserID &uid)
+{
+ return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
+}
+
+QString Formatting::prettyName(const UserID::Signature &sig)
+{
+ return prettyName(OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
+}
+
+//
+// EMail
+//
+
+QString Formatting::prettyEMail(const Key &key)
+{
+ for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
+ const QString email = prettyEMail(key.userID(i));
+ if (!email.isEmpty()) {
+ return email;
+ }
+ }
+ return QString();
+}
+
+QString Formatting::prettyEMail(const UserID &uid)
+{
+ return prettyEMail(uid.email(), uid.id());
+}
+
+QString Formatting::prettyEMail(const UserID::Signature &sig)
+{
+ return prettyEMail(sig.signerEmail(), sig.signerUserID());
+}
+
+QString Formatting::prettyEMail(const char *email_, const char *id)
+{
+ QString email, name, comment;
+ if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_),
+ email, name, comment)) {
+ return email;
+ } else {
+ return DN(id)[QStringLiteral("EMAIL")].trimmed();
+ }
+}
+
+//
+// Tooltip
+//
+
+namespace
+{
+
+static QString protect_whitespace(QString s)
+{
+ static const QLatin1Char SP(' '), NBSP('\xA0');
+ return s.replace(SP, NBSP);
+}
+
+template <typename T_arg>
+QString format_row(const QString &field, const T_arg &arg)
+{
+ return i18n("<tr><th>%1:</th><td>%2</td></tr>", protect_whitespace(field), arg);
+}
+QString format_row(const QString &field, const QString &arg)
+{
+ return i18n("<tr><th>%1:</th><td>%2</td></tr>", protect_whitespace(field), arg.toHtmlEscaped());
+}
+QString format_row(const QString &field, const char *arg)
+{
+ return format_row(field, QString::fromUtf8(arg));
+}
+
+QString format_keytype(const Key &key)
+{
+ const Subkey subkey = key.subkey(0);
+ if (key.hasSecret()) {
+ return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
+ } else {
+ return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
+ }
+}
+
+QString format_keyusage(const Key &key)
+{
+ QStringList capabilities;
+ if (key.canReallySign()) {
+ if (key.isQualified()) {
+ capabilities.push_back(i18n("Signing EMails and Files (Qualified)"));
+ } else {
+ capabilities.push_back(i18n("Signing EMails and Files"));
+ }
+ }
+ if (key.canEncrypt()) {
+ capabilities.push_back(i18n("Encrypting EMails and Files"));
+ }
+ if (key.canCertify()) {
+ capabilities.push_back(i18n("Certifying other Certificates"));
+ }
+ if (key.canAuthenticate()) {
+ capabilities.push_back(i18n("Authenticate against Servers"));
+ }
+ return capabilities.join(QStringLiteral(", "));
+}
+
+static QString time_t2string(time_t t)
+{
+ QDateTime dt;
+ dt.setTime_t(t);
+ return QLocale().toString(dt, QLocale::ShortFormat);
+}
+
+static QString make_red(const QString &txt)
+{
+ return QLatin1String("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
+}
+
+}
+
+QString Formatting::toolTip(const Key &key, int flags)
+{
+ if (flags == 0 || (key.protocol() != CMS && key.protocol() != OpenPGP)) {
+ return QString();
+ }
+
+ const Subkey subkey = key.subkey(0);
+
+ QString result;
+ if (flags & Validity) {
+ if (key.protocol() == OpenPGP || (key.keyListMode() & Validate))
+ if (key.isRevoked()) {
+ result += make_red(i18n("This certificate has been revoked."));
+ } else if (key.isExpired()) {
+ result += make_red(i18n("This certificate has expired."));
+ } else if (key.isDisabled()) {
+ result += i18n("This certificate has been disabled locally.");
+ } else {
+ result += i18n("This certificate is currently valid.");
+ }
+ else {
+ result += i18n("The validity of this certificate cannot be checked at the moment.");
+ }
+ }
+ if (flags == Validity) {
+ return result;
+ }
+
+ result += QLatin1String("<table border=\"0\">");
+ if (key.protocol() == CMS) {
+ if (flags & SerialNumber) {
+ result += format_row(i18n("Serial number"), key.issuerSerial());
+ }
+ if (flags & Issuer) {
+ result += format_row(i18n("Issuer"), key.issuerName());
+ }
+ }
+ if (flags & UserIDs) {
+ const std::vector<UserID> uids = key.userIDs();
+ if (!uids.empty())
+ result += format_row(key.protocol() == CMS
+ ? i18n("Subject")
+ : i18n("User-ID"), prettyUserID(uids.front()));
+ if (uids.size() > 1)
+ for (std::vector<UserID>::const_iterator it = uids.begin() + 1, end = uids.end(); it != end; ++it)
+ if (!it->isRevoked() && !it->isInvalid()) {
+ result += format_row(i18n("a.k.a."), prettyUserID(*it));
+ }
+ }
+ if (flags & ExpiryDates)
+ result += format_row(i18n("Validity"),
+ subkey.neverExpires()
+ ? i18n("from %1 until forever", time_t2string(subkey.creationTime()))
+ : i18n("from %1 through %2", time_t2string(subkey.creationTime()), time_t2string(subkey.expirationTime())));
+ if (flags & CertificateType) {
+ result += format_row(i18n("Certificate type"), format_keytype(key));
+ }
+ if (flags & CertificateUsage) {
+ result += format_row(i18n("Certificate usage"), format_keyusage(key));
+ }
+ if (flags & KeyID) {
+ result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID()));
+ }
+ if (flags & Fingerprint) {
+ result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
+ }
+ if (flags & OwnerTrust) {
+ if (key.protocol() == OpenPGP) {
+ result += format_row(i18n("Ownertrust"), ownerTrustShort(key));
+ } else if (key.isRoot()) {
+ result += format_row(i18n("Trusted issuer?"),
+ key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") :
+ /* else */ i18n("No"));
+ }
+ }
+
+ if (flags & StorageLocation) {
+ if (const char *card = subkey.cardSerialNumber()) {
+ result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
+ } else {
+ result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
+ }
+ }
+ result += QLatin1String("</table>");
+
+ return result;
+}
+
+//
+// Creation and Expiration
+//
+
+namespace
+{
+static QDate time_t2date(time_t t)
+{
+ if (!t) {
+ return QDate();
+ }
+ QDateTime dt;
+ dt.setTime_t(t);
+ return dt.date();
+}
+static QString date2string(const QDate &date)
+{
+ return QLocale().toString(date, QLocale::ShortFormat);
+}
+
+template <typename T>
+QString expiration_date_string(const T &tee)
+{
+ return tee.neverExpires() ? QString() : date2string(time_t2date(tee.expirationTime()));
+}
+template <typename T>
+QDate creation_date(const T &tee)
+{
+ return time_t2date(tee.creationTime());
+}
+template <typename T>
+QDate expiration_date(const T &tee)
+{
+ return time_t2date(tee.expirationTime());
+}
+}
+
+QString Formatting::expirationDateString(const Key &key)
+{
+ return expiration_date_string(key.subkey(0));
+}
+
+QString Formatting::expirationDateString(const Subkey &subkey)
+{
+ return expiration_date_string(subkey);
+}
+
+QString Formatting::expirationDateString(const UserID::Signature &sig)
+{
+ return expiration_date_string(sig);
+}
+
+QDate Formatting::expirationDate(const Key &key)
+{
+ return expiration_date(key.subkey(0));
+}
+
+QDate Formatting::expirationDate(const Subkey &subkey)
+{
+ return expiration_date(subkey);
+}
+
+QDate Formatting::expirationDate(const UserID::Signature &sig)
+{
+ return expiration_date(sig);
+}
+
+QString Formatting::creationDateString(const Key &key)
+{
+ return date2string(creation_date(key.subkey(0)));
+}
+
+QString Formatting::creationDateString(const Subkey &subkey)
+{
+ return date2string(creation_date(subkey));
+}
+
+QString Formatting::creationDateString(const UserID::Signature &sig)
+{
+ return date2string(creation_date(sig));
+}
+
+QDate Formatting::creationDate(const Key &key)
+{
+ return creation_date(key.subkey(0));
+}
+
+QDate Formatting::creationDate(const Subkey &subkey)
+{
+ return creation_date(subkey);
+}
+
+QDate Formatting::creationDate(const UserID::Signature &sig)
+{
+ return creation_date(sig);
+}
+
+//
+// Types
+//
+
+QString Formatting::displayName(Protocol p)
+{
+ if (p == CMS) {
+ return i18nc("X.509/CMS encryption standard", "X.509");
+ }
+ if (p == OpenPGP) {
+ return i18n("OpenPGP");
+ }
+ return i18nc("Unknown encryption protocol", "Unknown");
+}
+
+QString Formatting::type(const Key &key)
+{
+ return displayName(key.protocol());
+}
+
+QString Formatting::type(const Subkey &subkey)
+{
+ return QString::fromUtf8(subkey.publicKeyAlgorithmAsString());
+}
+
+//
+// Status / Validity
+//
+
+QString Formatting::ownerTrustShort(const Key &key)
+{
+ return ownerTrustShort(key.ownerTrust());
+}
+
+QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
+{
+ switch (trust) {
+ case Key::Unknown: return i18nc("unknown trust level", "unknown");
+ case Key::Never: return i18n("untrusted");
+ case Key::Marginal: return i18nc("marginal trust", "marginal");
+ case Key::Full: return i18nc("full trust", "full");
+ case Key::Ultimate: return i18nc("ultimate trust", "ultimate");
+ case Key::Undefined: return i18nc("undefined trust", "undefined");
+ default:
+ assert(!"unexpected owner trust value");
+ break;
+ }
+ return QString();
+}
+
+QString Formatting::validityShort(const Subkey &subkey)
+{
+ if (subkey.isRevoked()) {
+ return i18n("revoked");
+ }
+ if (subkey.isExpired()) {
+ return i18n("expired");
+ }
+ if (subkey.isDisabled()) {
+ return i18n("disabled");
+ }
+ if (subkey.isInvalid()) {
+ return i18n("invalid");
+ }
+ return i18nc("as in good/valid signature", "good");
+}
+
+QString Formatting::validityShort(const UserID &uid)
+{
+ if (uid.isRevoked()) {
+ return i18n("revoked");
+ }
+ if (uid.isInvalid()) {
+ return i18n("invalid");
+ }
+ switch (uid.validity()) {
+ case UserID::Unknown: return i18nc("unknown trust level", "unknown");
+ case UserID::Undefined: return i18nc("undefined trust", "undefined");
+ case UserID::Never: return i18n("untrusted");
+ case UserID::Marginal: return i18nc("marginal trust", "marginal");
+ case UserID::Full: return i18nc("full trust", "full");
+ case UserID::Ultimate: return i18nc("ultimate trust", "ultimate");
+ }
+ return QString();
+}
+
+QString Formatting::validityShort(const UserID::Signature &sig)
+{
+ switch (sig.status()) {
+ case UserID::Signature::NoError:
+ if (!sig.isInvalid()) {
+ if (sig.certClass() > 0) {
+ return i18n("class %1", sig.certClass());
+ } else {
+ return i18nc("good/valid signature", "good");
+ }
+ }
+ // fall through:
+ case UserID::Signature::GeneralError:
+ return i18n("invalid");
+ case UserID::Signature::SigExpired: return i18n("expired");
+ case UserID::Signature::KeyExpired: return i18n("certificate expired");
+ case UserID::Signature::BadSignature: return i18nc("fake/invalid signature", "bad");
+ case UserID::Signature::NoPublicKey: return QString();
+ }
+ return QString();
+}
+
+QString Formatting::formatKeyLink(const Key &key)
+{
+ if (key.isNull()) {
+ return QString();
+ }
+ return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key));
+}
+
+QString Formatting::formatForComboBox(const GpgME::Key &key)
+{
+ const QString name = prettyName(key);
+ QString mail = prettyEMail(key);
+ if (!mail.isEmpty()) {
+ mail = QLatin1Char('<') + mail + QLatin1Char('>');
+ }
+ return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified();
+}
+
+namespace
+{
+
+static QString keyToString(const Key &key)
+{
+
+ assert(!key.isNull());
+
+ const QString email = Formatting::prettyEMail(key);
+ const QString name = Formatting::prettyName(key);
+
+ if (name.isEmpty()) {
+ return email;
+ } else if (email.isEmpty()) {
+ return name;
+ } else {
+ return QStringLiteral("%1 <%2>").arg(name, email);
+ }
+}
+
+}
+
+const char *Formatting::summaryToString(const Signature::Summary summary)
+{
+ if (summary & Signature::Red) {
+ return "RED";
+ }
+ if (summary & Signature::Green) {
+ return "GREEN";
+ }
+ return "YELLOW";
+}
+
+QString Formatting::signatureToString(const Signature &sig, const Key &key)
+{
+ if (sig.isNull()) {
+ return QString();
+ }
+
+ const bool red = (sig.summary() & Signature::Red);
+ const bool valid = (sig.summary() & Signature::Valid);
+
+ if (red)
+ if (key.isNull())
+ if (const char *fpr = sig.fingerprint()) {
+ return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString()));
+ } else {
+ return i18n("Bad signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString()));
+ }
+ else {
+ return i18n("Bad signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString()));
+ }
+
+ else if (valid)
+ if (key.isNull())
+ if (const char *fpr = sig.fingerprint()) {
+ return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr));
+ } else {
+ return i18n("Good signature by an unknown certificate.");
+ }
+ else {
+ return i18n("Good signature by %1.", keyToString(key));
+ }
+
+ else if (key.isNull())
+ if (const char *fpr = sig.fingerprint()) {
+ return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString()));
+ } else {
+ return i18n("Invalid signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString()));
+ }
+ else {
+ return i18n("Invalid signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString()));
+ }
+}
+
+//
+// ImportResult
+//
+
+QString Formatting::importMetaData(const Import &import, const QStringList &ids)
+{
+ const QString result = importMetaData(import);
+ if (result.isEmpty()) {
+ return QString();
+ } else
+ return result + QLatin1Char('\n') +
+ i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') +
+ ids.join(QStringLiteral("\n"));
+}
+
+QString Formatting::importMetaData(const Import &import)
+{
+
+ if (import.isNull()) {
+ return QString();
+ }
+
+ if (import.error().isCanceled()) {
+ return i18n("The import of this certificate was canceled.");
+ }
+ if (import.error())
+ return i18n("An error occurred importing this certificate: %1",
+ QString::fromLocal8Bit(import.error().asString()));
+
+ const unsigned int status = import.status();
+ if (status & Import::NewKey)
+ return (status & Import::ContainedSecretKey)
+ ? i18n("This certificate was new to your keystore. The secret key is available.")
+ : i18n("This certificate is new to your keystore.");
+
+ QStringList results;
+ if (status & Import::NewUserIDs) {
+ results.push_back(i18n("New user-ids were added to this certificate by the import."));
+ }
+ if (status & Import::NewSignatures) {
+ results.push_back(i18n("New signatures were added to this certificate by the import."));
+ }
+ if (status & Import::NewSubkeys) {
+ results.push_back(i18n("New subkeys were added to this certificate by the import."));
+ }
+
+ return results.empty()
+ ? i18n("The import contained no new data for this certificate. It is unchanged.")
+ : results.join(QStringLiteral("\n"));
+}
+
+//
+// Overview in CertificateDetailsDialog
+//
+
+QString Formatting::formatOverview(const Key &key)
+{
+ return toolTip(key, AllOptions);
+}
+
+QString Formatting::usageString(const Subkey &sub)
+{
+ QStringList usageStrings;
+ if (sub.canCertify()) {
+ usageStrings << i18n("Certify");
+ }
+ if (sub.canSign()) {
+ usageStrings << i18n("Sign");
+ }
+ if (sub.canEncrypt()) {
+ usageStrings << i18n("Encrypt");
+ }
+ if (sub.canAuthenticate()) {
+ usageStrings << i18n("Authenticate");
+ }
+ return usageStrings.join(QStringLiteral(", "));
+}
+
+QString Formatting::summaryLine(const Key &key)
+{
+ return keyToString(key) + QStringLiteral(" ") +
+ i18nc("First arg is the Key Protocol OpenPGP or S/MIME, second arg is the creation date.",
+ "(%1 - created: %2)", displayName(key.protocol()) ,
+ Formatting::creationDateString(key));
+}
+
+// Icon for certificate selection indication
+QIcon Formatting::iconForUid(const UserID &uid)
+{
+ switch (uid.validity()) {
+ case UserID::Ultimate:
+ case UserID::Full:
+ case UserID::Marginal:
+ return QIcon::fromTheme(QStringLiteral("emblem-success"));
+ case UserID::Never:
+ return QIcon::fromTheme(QStringLiteral("emblem-error"));
+ case UserID::Undefined:
+ case UserID::Unknown:
+ default:
+ return QIcon::fromTheme(QStringLiteral("emblem-information"));
+ }
+}
+
+QString Formatting::validity(const UserID &uid)
+{
+ switch (uid.validity()) {
+ case UserID::Ultimate:
+ return i18n("The certificate is marked as your own.");
+ case UserID::Full:
+ return i18n("The certificate belongs to this recipient.");
+ case UserID::Marginal:
+ return i18n("The trust model indicates marginally that the certificate belongs to this recipient.");
+ case UserID::Never:
+ return i18n("This certificate should not be used.");
+ case UserID::Undefined:
+ case UserID::Unknown:
+ default:
+ return i18n("There is no indication that this certificate belongs to this recipient.");
+ }
+}
diff --git a/src/utils/formatting.h b/src/utils/formatting.h
new file mode 100644
index 0000000..a7ab499
--- /dev/null
+++ b/src/utils/formatting.h
@@ -0,0 +1,138 @@
+/* -*- mode: c++; c-basic-offset:4 -*-
+ utils/formatting.h
+
+ This file is part of Kleopatra, the KDE keymanager
+ Copyright (c) 2007 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
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KLEOPATRA_UTILS_FORMATTING_H__
+#define __KLEOPATRA_UTILS_FORMATTING_H__
+
+#include <gpgme++/key.h>
+
+#include <kleo_export.h>
+
+class QString;
+class QStringList;
+class QDate;
+class QIcon;
+
+namespace GpgME
+{
+class Import;
+}
+
+namespace Kleo
+{
+namespace Formatting
+{
+
+KLEO_EXPORT QString prettyNameAndEMail(int proto, const char *id, const char *name, const char *email, const char *comment);
+KLEO_EXPORT QString prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment);
+KLEO_EXPORT QString prettyNameAndEMail(const GpgME::Key &key);
+KLEO_EXPORT QString prettyNameAndEMail(const GpgME::UserID &key);
+
+KLEO_EXPORT QString prettyUserID(const GpgME::UserID &uid);
+KLEO_EXPORT QString prettyKeyID(const char *id);
+
+KLEO_EXPORT QString prettyName(int proto, const char *id, const char *name, const char *comment);
+KLEO_EXPORT QString prettyName(const GpgME::Key &key);
+KLEO_EXPORT QString prettyName(const GpgME::UserID &uid);
+KLEO_EXPORT QString prettyName(const GpgME::UserID::Signature &sig);
+
+KLEO_EXPORT QString prettyEMail(const char *email, const char *id);
+KLEO_EXPORT QString prettyEMail(const GpgME::Key &key);
+KLEO_EXPORT QString prettyEMail(const GpgME::UserID &uid);
+KLEO_EXPORT QString prettyEMail(const GpgME::UserID::Signature &sig);
+
+enum ToolTipOption {
+ KeyID = 0x001,
+ Validity = 0x002,
+ StorageLocation = 0x004,
+ SerialNumber = 0x008,
+ Issuer = 0x010,
+ Subject = 0x020,
+ ExpiryDates = 0x040,
+ CertificateType = 0x080,
+ CertificateUsage = 0x100,
+ Fingerprint = 0x200,
+ UserIDs = 0x400,
+ OwnerTrust = 0x800,
+
+ AllOptions = 0xfff
+};
+
+KLEO_EXPORT QString toolTip(const GpgME::Key &key, int opts);
+
+KLEO_EXPORT QString expirationDateString(const GpgME::Key &key);
+KLEO_EXPORT QString expirationDateString(const GpgME::Subkey &subkey);
+KLEO_EXPORT QString expirationDateString(const GpgME::UserID::Signature &sig);
+KLEO_EXPORT QDate expirationDate(const GpgME::Key &key);
+KLEO_EXPORT QDate expirationDate(const GpgME::Subkey &subkey);
+KLEO_EXPORT QDate expirationDate(const GpgME::UserID::Signature &sig);
+
+KLEO_EXPORT QString creationDateString(const GpgME::Key &key);
+KLEO_EXPORT QString creationDateString(const GpgME::Subkey &subkey);
+KLEO_EXPORT QString creationDateString(const GpgME::UserID::Signature &sig);
+KLEO_EXPORT QDate creationDate(const GpgME::Key &key);
+KLEO_EXPORT QDate creationDate(const GpgME::Subkey &subkey);
+KLEO_EXPORT QDate creationDate(const GpgME::UserID::Signature &sig);
+
+KLEO_EXPORT QString displayName(GpgME::Protocol prot);
+KLEO_EXPORT QString type(const GpgME::Key &key);
+KLEO_EXPORT QString type(const GpgME::Subkey &subkey);
+
+KLEO_EXPORT QString ownerTrustShort(const GpgME::Key &key);
+KLEO_EXPORT QString ownerTrustShort(GpgME::Key::OwnerTrust trust);
+
+KLEO_EXPORT QString validityShort(const GpgME::Subkey &subkey);
+KLEO_EXPORT QString validityShort(const GpgME::UserID &uid);
+KLEO_EXPORT QString validityShort(const GpgME::UserID::Signature &sig);
+/* A sentence about the validity of the UserID */
+KLEO_EXPORT QString validity(const GpgME::UserID &uid);
+
+KLEO_EXPORT QString formatForComboBox(const GpgME::Key &key);
+
+KLEO_EXPORT QString formatKeyLink(const GpgME::Key &key);
+
+KLEO_EXPORT QString signatureToString(const GpgME::Signature &sig, const GpgME::Key &key);
+
+KLEO_EXPORT const char *summaryToString(const GpgME::Signature::Summary summary);
+
+KLEO_EXPORT QString importMetaData(const GpgME::Import &import);
+KLEO_EXPORT QString importMetaData(const GpgME::Import &import, const QStringList &sources);
+
+KLEO_EXPORT QString formatOverview(const GpgME::Key &key);
+KLEO_EXPORT QString usageString(const GpgME::Subkey &subkey);
+KLEO_EXPORT QString summaryLine(const GpgME::Key &key);
+
+KLEO_EXPORT QIcon iconForUid(const GpgME::UserID &uid);
+}
+}
+
+#endif /* __KLEOPATRA_UTILS_FORMATTING_H__ */