Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F26445979
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
98 KB
Subscribers
None
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
index afd3b48af..c95ed536e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,250 +1,250 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "11")
set(RELEASE_SERVICE_VERSION_MICRO "70")
# The RELEASE_SERVICE_VERSION is used by Gpg4win to add the Gpg4win version
if (NOT RELEASE_SERVICE_VERSION)
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
endif()
if(RELEASE_SERVICE_VERSION_MICRO LESS 10)
set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}0${RELEASE_SERVICE_VERSION_MICRO}")
else()
set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}${RELEASE_SERVICE_VERSION_MICRO}")
endif()
set(KLEOPATRA_VERSION_MAJOR "3")
set(KLEOPATRA_VERSION_MINOR "2")
set(KLEOPATRA_VERSION_MICRO "0")
set(kleopatra_version "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}.${KDE_APPLICATIONS_COMPACT_VERSION}")
# The following is for Windows
set(kleopatra_version_win "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}")
set(kleopatra_fileversion_win "${KLEOPATRA_VERSION_MAJOR},${KLEOPATRA_VERSION_MINOR},${KLEOPATRA_VERSION_MICRO},0")
if (NOT KLEOPATRA_DISTRIBUTION_TEXT)
# This is only used on Windows for the file attributes of Kleopatra
set(KLEOPATRA_DISTRIBUTION_TEXT "KDE")
endif()
project(kleopatra VERSION ${kleopatra_version})
option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF)
# Standalone build. Find / include everything necessary.
set(KF_MIN_VERSION "6.4.0")
set(KIDENTITYMANAGEMENT_VERSION "6.2.40")
set(KMAILTRANSPORT_VERSION "6.2.40")
set(AKONADI_MIME_VERSION "6.2.40")
set(KMIME_VERSION "6.2.40")
-set(LIBKLEO_VERSION "6.2.40")
+set(LIBKLEO_VERSION "6.2.41")
set(QT_REQUIRED_VERSION "6.7.0")
set(MIMETREEPARSER_VERSION "6.2.40")
set(GPGME_REQUIRED_VERSION "1.21.0")
set(LIBASSUAN_REQUIRED_VERSION "2.4.2")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
if (WIN32)
set(KF6_WANT_VERSION ${KF_MIN_VERSION})
set(KMIME_WANT_VERSION ${KMIME_VERSION})
else ()
set(KF6_WANT_VERSION ${KF_MIN_VERSION})
set(KMIME_WANT_VERSION ${KMIME_VERSION})
endif ()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF6_WANT_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(ECMInstallIcons)
include(ECMSetupVersion)
include(ECMAddTests)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
include(FeatureSummary)
include(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMFeatureSummary)
include(KDEClangFormat)
include(KDEGitCommitHooks)
# Find KF6 packages
find_package(KF6 ${KF6_WANT_VERSION}
REQUIRED COMPONENTS
Codecs
ColorScheme
Config
CoreAddons
Crash
I18n
IconThemes
ItemModels
KIO
WidgetsAddons
WindowSystem
XmlGui
StatusNotifierItem
OPTIONAL_COMPONENTS
DocTools
)
set_package_properties(KF6DocTools PROPERTIES
DESCRIPTION "Documentation tools"
PURPOSE "Required to generate Kleopatra documentation."
TYPE OPTIONAL)
# Optional packages
if (WIN32)
# Only a replacement available for Windows so this
# is required on other platforms.
find_package(KF6DBusAddons ${KF6_WANT_VERSION} CONFIG)
set_package_properties(KF6DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus"
PURPOSE "DBus session integration"
URL "https://inqlude.org/libraries/kdbusaddons.html"
TYPE OPTIONAL)
else()
find_package(KF6DBusAddons ${KF6_WANT_VERSION} CONFIG REQUIRED)
set(_kleopatra_dbusaddons_libs KF6::DBusAddons)
endif()
set(HAVE_QDBUS ${Qt6DBus_FOUND})
find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
set(QGPGME_NAME "QGpgmeQt6")
find_package(${QGPGME_NAME} ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.22.0")
set(QGPGME_HAS_TOLOGSTRING 1)
set(QGPGME_SUPPORTS_IS_MIME 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.23.0")
set(QGPGME_SUPPORTS_WKD_REFRESH_JOB 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.23.3")
set(QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO 1)
set(QGPGME_IMPORT_JOB_SUPPORTS_IMPORT_OPTIONS 1)
endif()
find_package(KPim6Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KPim6Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED)
find_package(KPim6IdentityManagementCore ${KIDENTITYMANAGEMENT_VERSION} CONFIG)
find_package(KPim6MailTransport ${KMAILTRANSPORT_VERSION} CONFIG)
find_package(KPim6AkonadiMime ${AKONADI_MIME_VERSION} CONFIG)
find_package(KPim6MimeTreeParserWidgets ${MIMETREEPARSER_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${LIBKLEO_MODULE_PATH} ${CMAKE_MODULE_PATH})
find_package(Qt6 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport)
find_package(LibAssuan ${LIBASSUAN_REQUIRED_VERSION} REQUIRED)
set_package_properties(LibAssuan PROPERTIES
TYPE REQUIRED
PURPOSE "Needed for Kleopatra to act as the GnuPG UI Server"
)
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
set(kleopatra_release FALSE)
if(NOT kleopatra_release)
find_package(Git)
if(GIT_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE rc
ERROR_QUIET)
if(rc EQUAL 0)
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%h ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE Kleopatra_WC_REVISION)
string(REGEX REPLACE "\n" "" Kleopatra_WC_REVISION "${Kleopatra_WC_REVISION}")
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%cI ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE Kleopatra_WC_LAST_CHANGED_DATE)
string(REGEX REPLACE "^([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+).*$" "\\1\\2\\3T\\4\\5\\6"
Kleopatra_WC_LAST_CHANGED_DATE "${Kleopatra_WC_LAST_CHANGED_DATE}")
set(kleopatra_version "${kleopatra_version}+git${Kleopatra_WC_LAST_CHANGED_DATE}~${Kleopatra_WC_REVISION}")
endif()
endif()
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-kleopatra.h.in ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
add_definitions(-DQT_NO_CONTEXTLESS_CONNECT)
ecm_set_disabled_deprecation_versions(QT 6.7.2 KF 6.4.0)
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers")
endif()
if(MINGW)
# we do not care about different signedness of passed pointer arguments
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
endif()
add_definitions(-DQT_NO_EMIT)
remove_definitions(-DQT_NO_FOREACH)
# Disable the use of QStringBuilder for operator+ to prevent crashes when
# returning the result of concatenating string temporaries in lambdas. We do
# this for example in some std::transform expressions.
# This is a known issue: https://bugreports.qt.io/browse/QTBUG-47066
# Alternatively, one would always have to remember to force the lambdas to
# return a QString instead of QStringBuilder, but that's just too easy to
# forget and, unfortunately, the compiler doesn't issue a warning if one forgets
# this. So, it's just too dangerous.
# One can still use QStringBuilder explicitly with the operator% if necessary.
remove_definitions(-DQT_USE_FAST_OPERATOR_PLUS)
remove_definitions(-DQT_USE_QSTRINGBUILDER)
kde_enable_exceptions()
option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF)
if (USE_UNITY_CMAKE_SUPPORT)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON)
endif()
add_subdirectory(pics)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(tests)
add_subdirectory(autotests)
endif()
ecm_qt_install_logging_categories(
EXPORT KLEOPATRA
FILE kleopatra.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
ki18n_install(po)
if(KF6DocTools_FOUND)
kdoctools_install(po)
add_subdirectory(doc)
endif()
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
# add clang-format target for all our real source files
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/src/dialogs/lookupcertificatesdialog.cpp b/src/dialogs/lookupcertificatesdialog.cpp
index 14a26f9d3..e1b798023 100644
--- a/src/dialogs/lookupcertificatesdialog.cpp
+++ b/src/dialogs/lookupcertificatesdialog.cpp
@@ -1,523 +1,515 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/lookupcertificatesdialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "lookupcertificatesdialog.h"
#include <view/textoverlay.h>
#include <kleopatra_debug.h>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyList>
#include <Libkleo/TreeWidget>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardAction>
#include <QClipboard>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGuiApplication>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QPushButton>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QTreeView>
#include <QVBoxLayout>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
static const int KeyWithOriginRole = 0x201;
class LookupCertificatesDialog::Private
{
friend class ::Kleo::Dialogs::LookupCertificatesDialog;
LookupCertificatesDialog *const q;
public:
enum Columns {
Name,
Email,
Fingerprint,
ValidFrom,
ValidUntil,
Protocol,
KeyID,
Origin,
};
explicit Private(LookupCertificatesDialog *qq);
~Private();
private:
void slotSelectionChanged()
{
enableDisableWidgets();
}
void slotSearchTextChanged()
{
enableDisableWidgets();
}
void slotSearchClicked()
{
Q_EMIT q->searchTextChanged(searchText());
}
void slotDetailsClicked()
{
if (!q->selectedCertificates().empty()) {
Q_EMIT q->detailsRequested(q->selectedCertificates().front().key);
}
}
void slotSaveAsClicked()
{
std::vector<GpgME::Key> keys;
for (const auto &[key, origin] : q->selectedCertificates()) {
keys.push_back(key);
}
Q_EMIT q->saveAsRequested(keys);
}
void readConfig();
void writeConfig();
void enableDisableWidgets();
QString searchText() const
{
return ui.findED->text().trimmed();
}
std::vector<KeyWithOrigin> selectedCertificates() const
{
const QAbstractItemView *const view = ui.resultTV;
if (!view) {
return {};
}
const auto sm = view->selectionModel();
Q_ASSERT(sm);
std::vector<KeyWithOrigin> keys;
for (const auto &index : sm->selectedRows()) {
const auto key = ui.resultTV->itemFromIndex(index)->data(Private::Name, KeyWithOriginRole).value<KeyWithOrigin>();
Q_ASSERT(!key.key.isNull());
keys.push_back(key);
}
return keys;
}
int numSelectedCertificates() const
{
return ui.resultTV->selectedItems().size();
}
void copySelectedValue()
{
- auto clipboardData = ui.resultTV->currentIndex().data(KeyList::ClipboardRole).toString();
+ auto clipboardData = ui.resultTV->currentIndex().data(Kleo::ClipboardRole).toString();
if (clipboardData.isEmpty()) {
clipboardData = ui.resultTV->currentIndex().data().toString();
}
QGuiApplication::clipboard()->setText(clipboardData);
}
QValidator *queryValidator();
void updateQueryMode();
private:
QueryMode queryMode = AnyQuery;
bool passive;
QValidator *anyQueryValidator = nullptr;
QValidator *emailQueryValidator = nullptr;
struct Ui {
QLabel *guidanceLabel;
QLabel *findLB;
QLineEdit *findED;
QPushButton *findPB;
Kleo::TreeWidget *resultTV;
TextOverlay *overlay;
QPushButton *selectAllPB;
QPushButton *deselectAllPB;
QPushButton *detailsPB;
QPushButton *saveAsPB;
QDialogButtonBox *buttonBox;
void setupUi(LookupCertificatesDialog *dialog)
{
auto verticalLayout = new QVBoxLayout{dialog};
auto gridLayout = new QGridLayout{};
int row = 0;
guidanceLabel = new QLabel{dialog};
gridLayout->addWidget(guidanceLabel, row, 0, 1, 3);
row++;
findLB = new QLabel{i18n("Find:"), dialog};
gridLayout->addWidget(findLB, row, 0, 1, 1);
findED = new QLineEdit{dialog};
findLB->setBuddy(findED);
gridLayout->addWidget(findED, row, 1, 1, 1);
findPB = new QPushButton{i18n("Search"), dialog};
findPB->setAutoDefault(false);
gridLayout->addWidget(findPB, row, 2, 1, 1);
row++;
gridLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}, row, 0, 1, 3);
row++;
resultTV = new Kleo::TreeWidget(dialog);
resultTV->setEnabled(true);
resultTV->setMinimumSize(QSize(400, 0));
overlay = new TextOverlay{resultTV, dialog};
overlay->hide();
gridLayout->addWidget(resultTV, row, 0, 1, 2);
auto buttonLayout = new QVBoxLayout{};
selectAllPB = new QPushButton{i18n("Select All"), dialog};
selectAllPB->setEnabled(false);
selectAllPB->setAutoDefault(false);
buttonLayout->addWidget(selectAllPB);
deselectAllPB = new QPushButton{i18n("Deselect All"), dialog};
deselectAllPB->setEnabled(false);
deselectAllPB->setAutoDefault(false);
buttonLayout->addWidget(deselectAllPB);
buttonLayout->addStretch();
detailsPB = new QPushButton{i18n("Details..."), dialog};
detailsPB->setEnabled(false);
detailsPB->setAutoDefault(false);
buttonLayout->addWidget(detailsPB);
saveAsPB = new QPushButton{i18n("Save As..."), dialog};
saveAsPB->setEnabled(false);
saveAsPB->setAutoDefault(false);
buttonLayout->addWidget(saveAsPB);
gridLayout->addLayout(buttonLayout, row, 2, 1, 1);
verticalLayout->addLayout(gridLayout);
buttonBox = new QDialogButtonBox{dialog};
buttonBox->setStandardButtons(QDialogButtonBox::Close | QDialogButtonBox::Save);
verticalLayout->addWidget(buttonBox);
QObject::connect(findED, SIGNAL(returnPressed()), findPB, SLOT(animateClick()));
QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
QObject::connect(findPB, SIGNAL(clicked()), dialog, SLOT(slotSearchClicked()));
QObject::connect(detailsPB, SIGNAL(clicked()), dialog, SLOT(slotDetailsClicked()));
QObject::connect(saveAsPB, SIGNAL(clicked()), dialog, SLOT(slotSaveAsClicked()));
QObject::connect(findED, SIGNAL(textChanged(QString)), dialog, SLOT(slotSearchTextChanged()));
QMetaObject::connectSlotsByName(dialog);
resultTV->setHeaderLabels({
i18nc("@title:column", "Name"),
i18nc("@title:column", "Email"),
i18nc("@title:column", "Fingerprint"),
i18nc("@title:column", "Valid From"),
i18nc("@title:column", "Valid Until"),
i18nc("@title:column", "Protocol"),
i18nc("@title:column", "Key ID"),
i18nc("@title:column", "Origin"),
});
resultTV->setSelectionMode(QAbstractItemView::ExtendedSelection);
resultTV->setContextMenuPolicy(Qt::CustomContextMenu);
connect(resultTV, &QTreeView::customContextMenuRequested, dialog, [this, dialog](const auto &pos) {
auto menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose, true);
menu->addAction(KStandardAction::copy(
dialog,
[dialog]() {
dialog->d->copySelectedValue();
},
dialog));
menu->popup(resultTV->mapToGlobal(pos));
});
}
explicit Ui(LookupCertificatesDialog *q)
{
q->setWindowTitle(i18nc("@title:window", "Lookup on Server"));
setupUi(q);
saveAsPB->hide(); // ### not yet implemented in LookupCertificatesCommand
findED->setClearButtonEnabled(true);
importPB()->setText(i18n("Import"));
importPB()->setEnabled(false);
connect(resultTV, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(slotDetailsClicked()));
findED->setFocus();
connect(selectAllPB, &QPushButton::clicked, resultTV, &QTreeView::selectAll);
connect(deselectAllPB, &QPushButton::clicked, resultTV, &QTreeView::clearSelection);
}
QPushButton *importPB() const
{
return buttonBox->button(QDialogButtonBox::Save);
}
QPushButton *closePB() const
{
return buttonBox->button(QDialogButtonBox::Close);
}
} ui;
};
LookupCertificatesDialog::Private::Private(LookupCertificatesDialog *qq)
: q(qq)
, passive(false)
, ui(q)
{
connect(ui.resultTV->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), q, SLOT(slotSelectionChanged()));
updateQueryMode();
}
LookupCertificatesDialog::Private::~Private()
{
}
void LookupCertificatesDialog::Private::readConfig()
{
KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
if (!ui.resultTV->restoreColumnLayout(QStringLiteral("LookupCertificatesDialog"))) {
ui.resultTV->setColumnHidden(Private::KeyID, true);
}
const QSize size = configGroup.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
q->resize(size);
}
}
void LookupCertificatesDialog::Private::writeConfig()
{
KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
configGroup.writeEntry("Size", q->size());
configGroup.sync();
}
static QString guidanceText(LookupCertificatesDialog::QueryMode mode)
{
switch (mode) {
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << mode;
[[fallthrough]];
case LookupCertificatesDialog::AnyQuery:
return xi18nc("@info", "Enter a search term to search for matching certificates.");
case LookupCertificatesDialog::EmailQuery:
return xi18nc("@info", "Enter an email address to search for matching certificates.");
};
}
QValidator *LookupCertificatesDialog::Private::queryValidator()
{
switch (queryMode) {
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << queryMode;
[[fallthrough]];
case AnyQuery: {
if (!anyQueryValidator) {
// allow any query with at least one non-whitespace character
anyQueryValidator = new QRegularExpressionValidator{QRegularExpression{QStringLiteral(".*\\S+.*")}, q};
}
return anyQueryValidator;
}
case EmailQuery: {
if (!emailQueryValidator) {
// allow anything that looks remotely like an email address, i.e.
// anything with an '@' surrounded by non-whitespace characters
const QRegularExpression simpleEmailRegex{QStringLiteral(".*\\S+@\\S+.*")};
emailQueryValidator = new QRegularExpressionValidator{simpleEmailRegex, q};
}
return emailQueryValidator;
}
}
}
void LookupCertificatesDialog::Private::updateQueryMode()
{
ui.guidanceLabel->setText(guidanceText(queryMode));
ui.findED->setValidator(queryValidator());
}
LookupCertificatesDialog::LookupCertificatesDialog(QWidget *p, Qt::WindowFlags f)
: QDialog(p, f)
, d(new Private(this))
{
d->ui.findPB->setEnabled(false);
d->readConfig();
}
LookupCertificatesDialog::~LookupCertificatesDialog()
{
d->writeConfig();
}
void LookupCertificatesDialog::setQueryMode(QueryMode mode)
{
d->queryMode = mode;
d->updateQueryMode();
}
LookupCertificatesDialog::QueryMode LookupCertificatesDialog::queryMode() const
{
return d->queryMode;
}
void LookupCertificatesDialog::setCertificates(const std::vector<KeyWithOrigin> &certs)
{
d->ui.resultTV->setFocus();
d->ui.resultTV->clear();
for (const auto &[cert, origin] : certs) {
auto item = new QTreeWidgetItem;
item->setData(Private::Name, Qt::DisplayRole, Formatting::prettyName(cert));
item->setData(Private::Email, Qt::DisplayRole, Formatting::prettyEMail(cert));
item->setData(Private::Fingerprint, Qt::DisplayRole, Formatting::prettyID(cert.primaryFingerprint()));
item->setData(Private::Fingerprint, Qt::AccessibleTextRole, Formatting::accessibleHexID(cert.primaryFingerprint()));
- item->setData(Private::Fingerprint, KeyList::ClipboardRole, QString::fromLatin1(cert.primaryFingerprint()));
+ item->setData(Private::Fingerprint, Kleo::ClipboardRole, QString::fromLatin1(cert.primaryFingerprint()));
item->setData(Private::ValidFrom, Qt::DisplayRole, Formatting::creationDateString(cert));
item->setData(Private::ValidFrom, Qt::AccessibleTextRole, Formatting::accessibleCreationDate(cert));
item->setData(Private::ValidUntil, Qt::DisplayRole, Formatting::expirationDateString(cert));
item->setData(Private::ValidUntil, Qt::AccessibleTextRole, Formatting::accessibleExpirationDate(cert));
item->setData(Private::KeyID, Qt::DisplayRole, Formatting::prettyID(cert.keyID()));
item->setData(Private::KeyID, Qt::AccessibleTextRole, Formatting::accessibleHexID(cert.keyID()));
- item->setData(Private::KeyID, KeyList::ClipboardRole, QString::fromLatin1(cert.keyID()));
+ item->setData(Private::KeyID, Kleo::ClipboardRole, QString::fromLatin1(cert.keyID()));
if (cert.protocol() == Protocol::CMS) {
item->setData(Private::Origin, Qt::DisplayRole, i18n("LDAP"));
} else if (origin == GpgME::Key::OriginKS) {
if (keyserver().startsWith(QStringLiteral("ldap:")) || keyserver().startsWith(QStringLiteral("ldaps:"))) {
item->setData(Private::Origin, Qt::DisplayRole, i18n("LDAP"));
} else {
item->setData(Private::Origin, Qt::DisplayRole, i18n("Keyserver"));
}
} else {
item->setData(Private::Origin, Qt::DisplayRole, Formatting::origin(origin));
}
item->setData(Private::Protocol, Qt::DisplayRole, Formatting::displayName(cert.protocol()));
item->setData(Private::Name, KeyWithOriginRole, QVariant::fromValue(KeyWithOrigin{cert, origin}));
d->ui.resultTV->addTopLevelItem(item);
}
if (certs.size() == 1) {
d->ui.resultTV->setCurrentIndex(d->ui.resultTV->model()->index(0, 0));
}
}
std::vector<KeyWithOrigin> LookupCertificatesDialog::selectedCertificates() const
{
return d->selectedCertificates();
}
void LookupCertificatesDialog::setPassive(bool on)
{
if (d->passive == on) {
return;
}
d->passive = on;
d->enableDisableWidgets();
}
bool LookupCertificatesDialog::isPassive() const
{
return d->passive;
}
void LookupCertificatesDialog::setSearchText(const QString &text)
{
d->ui.findED->setText(text);
}
QString LookupCertificatesDialog::searchText() const
{
return d->ui.findED->text();
}
void LookupCertificatesDialog::setOverlayText(const QString &text)
{
if (text.isEmpty()) {
d->ui.overlay->hideOverlay();
} else {
d->ui.overlay->setText(text);
d->ui.overlay->showOverlay();
}
d->ui.selectAllPB->setEnabled(text.isEmpty());
d->ui.deselectAllPB->setEnabled(text.isEmpty());
}
QString LookupCertificatesDialog::overlayText() const
{
return d->ui.overlay->text();
}
void LookupCertificatesDialog::accept()
{
Q_ASSERT(!selectedCertificates().empty());
Q_EMIT importRequested(selectedCertificates());
QDialog::accept();
}
void LookupCertificatesDialog::Private::enableDisableWidgets()
{
// enable/disable everything except 'close', based on passive:
const QList<QObject *> list = q->children();
for (QObject *const o : list) {
if (QWidget *const w = qobject_cast<QWidget *>(o)) {
w->setDisabled(passive && w != ui.closePB() && w != ui.buttonBox);
}
}
if (passive) {
return;
}
q->setOverlayText({});
ui.findPB->setEnabled(ui.findED->hasAcceptableInput());
const int n = q->selectedCertificates().size();
ui.detailsPB->setEnabled(n == 1);
ui.saveAsPB->setEnabled(n == 1);
ui.importPB()->setEnabled(n != 0);
ui.importPB()->setDefault(false); // otherwise Import becomes default button if enabled and return triggers both a search and accept()
}
-void LookupCertificatesDialog::keyPressEvent(QKeyEvent *event)
-{
- if (event == QKeySequence::Copy && d->ui.resultTV->hasFocus()) {
- d->copySelectedValue();
- event->accept();
- }
-}
-
#include "moc_lookupcertificatesdialog.cpp"
diff --git a/src/dialogs/lookupcertificatesdialog.h b/src/dialogs/lookupcertificatesdialog.h
index 19f425783..c34363850 100644
--- a/src/dialogs/lookupcertificatesdialog.h
+++ b/src/dialogs/lookupcertificatesdialog.h
@@ -1,78 +1,75 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/lookupcertificatesdialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDialog>
#include <memory>
#include <vector>
#include <gpgme++/key.h>
struct KeyWithOrigin {
GpgME::Key key;
GpgME::Key::Origin origin;
};
namespace Kleo
{
namespace Dialogs
{
class LookupCertificatesDialog : public QDialog
{
Q_OBJECT
public:
enum QueryMode {
AnyQuery, //< any query is allowed
EmailQuery, //< only email queries are allowed
};
explicit LookupCertificatesDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~LookupCertificatesDialog() override;
void setQueryMode(QueryMode mode);
QueryMode queryMode() const;
void setCertificates(const std::vector<KeyWithOrigin> &certs);
std::vector<KeyWithOrigin> selectedCertificates() const;
void setPassive(bool passive);
bool isPassive() const;
void setSearchText(const QString &text);
QString searchText() const;
void setOverlayText(const QString &text);
QString overlayText() const;
Q_SIGNALS:
void searchTextChanged(const QString &text);
void saveAsRequested(const std::vector<GpgME::Key> &certs);
void importRequested(const std::vector<KeyWithOrigin> &certs);
void detailsRequested(const GpgME::Key &certs);
public Q_SLOTS:
void accept() override;
-protected:
- void keyPressEvent(QKeyEvent *event) override;
-
private:
class Private;
const std::unique_ptr<Private> d;
Q_PRIVATE_SLOT(d, void slotSearchTextChanged())
Q_PRIVATE_SLOT(d, void slotSearchClicked())
Q_PRIVATE_SLOT(d, void slotSelectionChanged())
Q_PRIVATE_SLOT(d, void slotDetailsClicked())
Q_PRIVATE_SLOT(d, void slotSaveAsClicked())
};
}
}
diff --git a/src/dialogs/revokerswidget.cpp b/src/dialogs/revokerswidget.cpp
index 3d928d93e..84b4bbb96 100644
--- a/src/dialogs/revokerswidget.cpp
+++ b/src/dialogs/revokerswidget.cpp
@@ -1,164 +1,157 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "revokerswidget.h"
#include "commands/command.h"
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyList>
#include <Libkleo/TreeWidget>
#include <gpgme++/key.h>
#include <gpgme.h>
#include <KLocalizedString>
#include <KStandardAction>
#include <QClipboard>
#include <QGuiApplication>
#include <QKeyEvent>
#include <QMenu>
#include <QVBoxLayout>
using namespace Kleo;
class RevokersWidget::Private
{
public:
RevokersWidget *const q;
enum Column {
Fingerprint,
Name,
Email,
};
Private(RevokersWidget *qq)
: q(qq)
, ui{qq}
{
connect(ui.revokersTree, &QTreeWidget::doubleClicked, q, [this]() {
const auto index = ui.revokersTree->currentIndex();
if (!index.isValid()) {
return;
}
#if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0
const auto fingerprint = QString::fromLatin1(key.revocationKey(ui.revokersTree->currentIndex().row()).fingerprint());
auto cmd = Command::commandForQuery(fingerprint);
cmd->setParentWidget(q->window());
cmd->start();
#endif
});
}
public:
GpgME::Key key;
public:
struct UI {
QVBoxLayout *mainLayout;
TreeWidget *revokersTree;
UI(QWidget *widget)
{
mainLayout = new QVBoxLayout{widget};
mainLayout->setContentsMargins({});
revokersTree = new TreeWidget{widget};
revokersTree->setProperty("_breeze_force_frame", true);
revokersTree->setHeaderLabels({
i18nc("@title:column", "Fingerprint"),
i18nc("@title:column", "Name"),
i18nc("@title:column", "Email"),
});
revokersTree->setAccessibleName(i18nc("@label", "Revokers"));
revokersTree->setContextMenuPolicy(Qt::CustomContextMenu);
revokersTree->setRootIsDecorated(false);
mainLayout->addWidget(revokersTree);
connect(revokersTree, &QTreeWidget::customContextMenuRequested, widget, [widget, this](const auto &pos) {
auto menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose, true);
menu->addAction(KStandardAction::copy(
widget,
[this]() {
- QGuiApplication::clipboard()->setText(revokersTree->currentIndex().data(KeyList::ClipboardRole).toString());
+ QGuiApplication::clipboard()->setText(revokersTree->currentIndex().data(Kleo::ClipboardRole).toString());
},
widget));
menu->popup(widget->mapToGlobal(pos));
});
}
} ui;
};
RevokersWidget::RevokersWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
}
RevokersWidget::~RevokersWidget() = default;
void RevokersWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() != GpgME::OpenPGP) {
return;
}
d->key = key;
d->ui.revokersTree->clear();
#if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0
for (size_t i = 0; i < key.numRevocationKeys(); i++) {
auto item = new QTreeWidgetItem;
auto revoker = key.revocationKey(i);
auto revokerKey = Kleo::KeyCache::instance()->findByFingerprint(revoker.fingerprint());
item->setData(Private::Fingerprint, Qt::DisplayRole, Formatting::prettyID(revoker.fingerprint()));
item->setData(Private::Fingerprint, Qt::AccessibleTextRole, Formatting::accessibleHexID(revoker.fingerprint()));
- item->setData(Private::Fingerprint, KeyList::ClipboardRole, QString::fromLatin1(revoker.fingerprint()));
+ item->setData(Private::Fingerprint, Kleo::ClipboardRole, QString::fromLatin1(revoker.fingerprint()));
if (!revokerKey.isNull()) {
item->setData(Private::Name, Qt::DisplayRole, Formatting::prettyName(revokerKey));
- item->setData(Private::Name, KeyList::ClipboardRole, Formatting::prettyName(revokerKey));
+ item->setData(Private::Name, Kleo::ClipboardRole, Formatting::prettyName(revokerKey));
item->setData(Private::Email, Qt::DisplayRole, Formatting::prettyEMail(revokerKey));
- item->setData(Private::Email, KeyList::ClipboardRole, Formatting::prettyEMail(revokerKey));
+ item->setData(Private::Email, Kleo::ClipboardRole, Formatting::prettyEMail(revokerKey));
} else {
item->setData(Private::Name, Qt::DisplayRole, {});
item->setData(Private::Email, Qt::DisplayRole, {});
item->setData(Private::Name, Qt::AccessibleTextRole, i18nc("text for screen readers for an unknown name", "unknown name"));
item->setData(Private::Email, Qt::AccessibleTextRole, i18nc("text for screen readers for an unknown email", "unknown email"));
- item->setData(Private::Name, KeyList::ClipboardRole, {});
- item->setData(Private::Email, KeyList::ClipboardRole, {});
+ item->setData(Private::Name, Kleo::ClipboardRole, {});
+ item->setData(Private::Email, Kleo::ClipboardRole, {});
}
d->ui.revokersTree->addTopLevelItem(item);
}
#endif
QMetaObject::invokeMethod(
this,
[this]() {
if (!d->ui.revokersTree->restoreColumnLayout(QStringLiteral("RevokersWidget"))) {
for (int i = 0; i < d->ui.revokersTree->columnCount(); i++) {
d->ui.revokersTree->resizeColumnToContents(i);
}
}
},
Qt::QueuedConnection);
}
GpgME::Key RevokersWidget::key() const
{
return d->key;
}
-void RevokersWidget::keyPressEvent(QKeyEvent *event)
-{
- if (event == QKeySequence::Copy) {
- QGuiApplication::clipboard()->setText(d->ui.revokersTree->currentIndex().data(KeyList::ClipboardRole).toString());
- }
-}
-
#include "moc_revokerswidget.cpp"
diff --git a/src/dialogs/revokerswidget.h b/src/dialogs/revokerswidget.h
index be8e5b4b4..ffcfa2f72 100644
--- a/src/dialogs/revokerswidget.h
+++ b/src/dialogs/revokerswidget.h
@@ -1,32 +1,29 @@
/* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDialog>
#include <QWidget>
namespace GpgME
{
class Key;
}
class RevokersWidget : public QWidget
{
Q_OBJECT
public:
explicit RevokersWidget(QWidget *parent = nullptr);
~RevokersWidget() override;
void setKey(const GpgME::Key &key);
GpgME::Key key() const;
-protected:
- void keyPressEvent(QKeyEvent *event) override;
-
private:
class Private;
const std::unique_ptr<Private> d;
};
diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp
index 9cff2460a..1b4dd800a 100644
--- a/src/view/keylistcontroller.cpp
+++ b/src/view/keylistcontroller.cpp
@@ -1,1106 +1,1106 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/keylistcontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "keylistcontroller.h"
#include "tabwidget.h"
#include <smartcard/readerstatus.h>
#include <utils/action_data.h>
#include "commands/exportcertificatecommand.h"
#include "commands/exportopenpgpcertstoservercommand.h"
#include "kleopatra_debug.h"
#include "tooltippreferences.h"
#include <settings.h>
#ifdef MAILAKONADI_ENABLED
#include "commands/exportopenpgpcerttoprovidercommand.h"
#endif // MAILAKONADI_ENABLED
#include "commands/adduseridcommand.h"
#include "commands/certificatetocardcommand.h"
#include "commands/certifycertificatecommand.h"
#include "commands/changeexpirycommand.h"
#include "commands/changeownertrustcommand.h"
#include "commands/changepassphrasecommand.h"
#include "commands/changeroottrustcommand.h"
#include "commands/checksumcreatefilescommand.h"
#include "commands/checksumverifyfilescommand.h"
#include "commands/clearcrlcachecommand.h"
#include "commands/creategroupcommand.h"
#include "commands/decryptverifyfilescommand.h"
#include "commands/deletecertificatescommand.h"
#include "commands/detailscommand.h"
#include "commands/dumpcertificatecommand.h"
#include "commands/dumpcrlcachecommand.h"
#include "commands/exportpaperkeycommand.h"
#include "commands/exportsecretkeycommand.h"
#include "commands/importcertificatefromfilecommand.h"
#include "commands/importcrlcommand.h"
#include "commands/lookupcertificatescommand.h"
#include "commands/newcertificatesigningrequestcommand.h"
#include "commands/newopenpgpcertificatecommand.h"
#include "commands/refreshcertificatescommand.h"
#include "commands/refreshopenpgpcertscommand.h"
#include "commands/refreshx509certscommand.h"
#include "commands/reloadkeyscommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/revokekeycommand.h"
#include "commands/signencryptfilescommand.h"
#include "commands/signencryptfoldercommand.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyListModel>
#include <gpgme++/key.h>
#include <KActionCollection>
#include <KLocalizedString>
#include <QAbstractItemView>
#include <QAction>
#include <QClipboard>
#include <QGuiApplication>
#include <QItemSelectionModel>
#include <QPointer>
#include <algorithm>
#include <iterator>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
namespace ranges = std::ranges;
class KeyListController::Private
{
friend class ::Kleo::KeyListController;
KeyListController *const q;
public:
explicit Private(KeyListController *qq);
~Private();
void connectView(QAbstractItemView *view);
void connectCommand(Command *cmd);
void connectTabWidget();
void disconnectTabWidget();
void addCommand(Command *cmd)
{
connectCommand(cmd);
commands.insert(ranges::lower_bound(commands, cmd), cmd);
}
void addView(QAbstractItemView *view)
{
connectView(view);
views.insert(ranges::lower_bound(views, view), view);
}
void removeView(QAbstractItemView *view)
{
view->disconnect(q);
view->selectionModel()->disconnect(q);
std::erase(views, view);
}
public:
void slotDestroyed(QObject *o)
{
qCDebug(KLEOPATRA_LOG) << (void *)o;
std::erase(views, o);
std::erase(commands, o);
}
void slotDoubleClicked(const QModelIndex &idx);
void slotActivated(const QModelIndex &idx);
void slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_);
void slotContextMenu(const QPoint &pos);
void slotCommandFinished();
void slotActionTriggered(QAction *action);
void slotCurrentViewChanged(QAbstractItemView *view)
{
if (view && !ranges::binary_search(views, view)) {
qCDebug(KLEOPATRA_LOG) << "you need to register view" << view << "before trying to set it as the current view!";
addView(view);
}
currentView = view;
q->enableDisableActions(view ? view->selectionModel() : nullptr);
}
private:
int toolTipOptions() const;
private:
static Command::Restrictions calculateRestrictionsMask(const QItemSelectionModel *sm);
private:
struct action_item {
QPointer<QAction> action;
Command::Restrictions restrictions;
Command *(*createCommand)(QAbstractItemView *, KeyListController *);
};
std::vector<action_item> actions;
std::vector<QAbstractItemView *> views;
std::vector<Command *> commands;
QPointer<QWidget> parentWidget;
QPointer<TabWidget> tabWidget;
QPointer<QAbstractItemView> currentView;
QPointer<AbstractKeyListModel> flatModel, hierarchicalModel;
std::vector<QMetaObject::Connection> m_connections;
};
KeyListController::Private::Private(KeyListController *qq)
: q(qq)
, actions()
, views()
, commands()
, parentWidget()
, tabWidget()
, flatModel()
, hierarchicalModel()
{
}
KeyListController::Private::~Private()
{
}
KeyListController::KeyListController(QObject *p)
: QObject(p)
, d(new Private(this))
{
}
KeyListController::~KeyListController()
{
}
void KeyListController::addView(QAbstractItemView *view)
{
if (!view || ranges::binary_search(d->views, view)) {
return;
}
d->addView(view);
}
void KeyListController::removeView(QAbstractItemView *view)
{
if (!view || !ranges::binary_search(d->views, view)) {
return;
}
d->removeView(view);
}
void KeyListController::setCurrentView(QAbstractItemView *view)
{
d->slotCurrentViewChanged(view);
}
std::vector<QAbstractItemView *> KeyListController::views() const
{
return d->views;
}
void KeyListController::setFlatModel(AbstractKeyListModel *model)
{
if (model == d->flatModel) {
return;
}
d->flatModel = model;
if (model) {
model->setToolTipOptions(d->toolTipOptions());
}
}
void KeyListController::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == d->hierarchicalModel) {
return;
}
d->hierarchicalModel = model;
if (model) {
model->setToolTipOptions(d->toolTipOptions());
}
}
void KeyListController::setTabWidget(TabWidget *tabWidget)
{
if (tabWidget == d->tabWidget) {
return;
}
d->disconnectTabWidget();
d->tabWidget = tabWidget;
d->connectTabWidget();
d->slotCurrentViewChanged(tabWidget ? tabWidget->currentView() : nullptr);
}
void KeyListController::setParentWidget(QWidget *parent)
{
d->parentWidget = parent;
}
QWidget *KeyListController::parentWidget() const
{
return d->parentWidget;
}
void KeyListController::Private::connectTabWidget()
{
if (!tabWidget) {
return;
}
const auto views = tabWidget->views();
ranges::for_each(views, [this](QAbstractItemView *view) {
addView(view);
});
m_connections.reserve(3);
m_connections.push_back(connect(tabWidget, &TabWidget::viewAdded, q, &KeyListController::addView));
m_connections.push_back(connect(tabWidget, &TabWidget::viewAboutToBeRemoved, q, &KeyListController::removeView));
m_connections.push_back(connect(tabWidget, &TabWidget::currentViewChanged, q, [this](QAbstractItemView *view) {
slotCurrentViewChanged(view);
}));
}
void KeyListController::Private::disconnectTabWidget()
{
if (!tabWidget) {
return;
}
for (const auto &connection : m_connections) {
disconnect(connection);
}
m_connections.clear();
const auto views = tabWidget->views();
ranges::for_each(views, [this](QAbstractItemView *view) {
removeView(view);
});
}
AbstractKeyListModel *KeyListController::flatModel() const
{
return d->flatModel;
}
AbstractKeyListModel *KeyListController::hierarchicalModel() const
{
return d->hierarchicalModel;
}
QAbstractItemView *KeyListController::currentView() const
{
return d->currentView;
}
TabWidget *KeyListController::tabWidget() const
{
return d->tabWidget;
}
void KeyListController::createActions(KActionCollection *coll)
{
const std::vector<action_data> common_and_openpgp_action_data = {
// File menu
{
"file_new_certificate",
i18n("New OpenPGP Key Pair..."),
i18n("Create a new OpenPGP certificate"),
"view-certificate-add",
nullptr,
nullptr,
QStringLiteral("Ctrl+N"),
},
{
"file_export_certificates",
i18n("Export..."),
i18n("Export the selected certificate (public key) to a file"),
"view-certificate-export",
nullptr,
nullptr,
QStringLiteral("Ctrl+E"),
},
{
"file_export_certificates_to_server",
i18n("Publish on Server..."),
keyserver().startsWith(QStringLiteral("ldap:")) || keyserver().startsWith(QStringLiteral("ldaps:"))
? i18n("Publish the selected certificate (public key) to internal directory")
: i18n("Publish the selected certificate (public key) on %1", keyserver()),
"view-certificate-export-server",
nullptr,
nullptr,
QStringLiteral("Ctrl+Shift+E"),
},
#ifdef MAILAKONADI_ENABLED
{
"file_export_certificate_to_provider",
i18n("Publish at Mail Provider..."),
i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"),
"view-certificate-export",
nullptr,
nullptr,
QString(),
},
#endif // MAILAKONADI_ENABLED
{
"file_export_secret_keys",
i18n("Backup Secret Keys..."),
QString(),
"view-certificate-export-secret",
nullptr,
nullptr,
QString(),
},
{
"file_export_paper_key",
i18n("Print Secret Key..."),
QString(),
"document-print",
nullptr,
nullptr,
QString(),
},
{
"file_lookup_certificates",
i18n("Lookup on Server..."),
keyserver().startsWith(QStringLiteral("ldap:")) || keyserver().startsWith(QStringLiteral("ldaps:"))
? i18n("Search for certificates in internal directory")
: i18n("Search for certificates on %1", keyserver()),
"edit-find",
nullptr,
nullptr,
QStringLiteral("Shift+Ctrl+I"),
},
{
"file_import_certificates",
i18n("Import..."),
i18n("Import a certificate from a file"),
"view-certificate-import",
nullptr,
nullptr,
QStringLiteral("Ctrl+I"),
},
{
"file_decrypt_verify_files",
i18n("Decrypt/Verify..."),
i18n("Decrypt and/or verify files"),
"document-edit-decrypt-verify",
nullptr,
nullptr,
QString(),
},
{
"file_sign_encrypt_files",
i18n("Sign/Encrypt..."),
i18n("Encrypt and/or sign files"),
"document-edit-sign-encrypt",
nullptr,
nullptr,
QString(),
},
{
"file_sign_encrypt_folder",
i18n("Sign/Encrypt Folder..."),
i18n("Encrypt and/or sign folders"),
"folder-edit-sign-encrypt-symbolic",
nullptr,
nullptr,
QString(),
},
{
"file_checksum_create_files",
i18n("Create Checksum Files..."),
QString(),
nullptr /*"document-checksum-create"*/,
nullptr,
nullptr,
QString(),
},
{
"file_checksum_verify_files",
i18n("Verify Checksum Files..."),
QString(),
nullptr /*"document-checksum-verify"*/,
nullptr,
nullptr,
QString(),
},
// View menu
{
"view_redisplay",
i18n("Redisplay"),
QString(),
"view-refresh",
nullptr,
nullptr,
QStringLiteral("F5"),
},
{
"view_stop_operations",
i18n("Stop Operation"),
QString(),
"process-stop",
this,
[this](bool) {
cancelCommands();
},
QStringLiteral("Escape"),
RegularQAction,
Disabled,
},
{
"view_certificate_details",
i18n("Details"),
QString(),
"dialog-information",
nullptr,
nullptr,
QString(),
},
// Certificate menu
{
"certificates_revoke",
i18n("Revoke Certificate..."),
i18n("Revoke the selected OpenPGP certificate"),
"view-certificate-revoke",
nullptr,
nullptr,
{},
},
{
"certificates_delete",
i18n("Delete"),
i18n("Delete selected certificates"),
"edit-delete",
nullptr,
nullptr,
QStringLiteral("Delete"),
},
{
"certificates_refresh",
i18n("Update Certificates"),
i18n("Update selected certificates"),
"view-refresh",
nullptr,
nullptr,
QString(),
},
{
"certificates_certify_certificate",
i18n("Certify..."),
i18n("Certify the validity of the selected certificate"),
"view-certificate-sign",
nullptr,
nullptr,
QString(),
},
{
"certificates_revoke_certification",
i18n("Revoke Certification..."),
i18n("Revoke the certification of the selected certificate"),
"view-certificate-revoke",
nullptr,
nullptr,
QString(),
},
{
"certificates_change_expiry",
i18n("Change End of Validity Period..."),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"certificates_change_owner_trust",
i18nc("@action:inmenu", "Change Certification Power..."),
i18nc("@info:tooltip", "Grant or revoke the certification power of the selected certificate"),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"certificates_change_passphrase",
i18n("Change Passphrase..."),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"certificates_add_userid",
i18n("Add User ID..."),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"certificates_create_group",
i18nc("@action:inmenu", "Create Group..."),
i18nc("@info:tooltip", "Create a group from the selected certificates"),
"resource-group-new",
nullptr,
nullptr,
QString(),
},
{
"certificate_to_card",
i18nc("@action:inmenu", "Copy to Card"),
i18nc("@info:tooltip", "Copy the selected certificate to a smartcard"),
"auth-sim-locked",
nullptr,
nullptr,
QString(),
},
// Tools menu
{
"tools_refresh_openpgp_certificates",
i18n("Refresh OpenPGP Certificates"),
QString(),
"view-refresh",
nullptr,
nullptr,
QString(),
},
// Context Menu
{
"cell_copy",
i18nc("@action:button", "Copy"),
QString(),
"edit-copy",
this,
[this](bool) {
- QGuiApplication::clipboard()->setText(currentView()->currentIndex().data(KeyList::ClipboardRole).toString());
+ QGuiApplication::clipboard()->setText(currentView()->currentIndex().data(Kleo::ClipboardRole).toString());
},
QStringLiteral("Ctrl+C"),
}
// Window menu
// (come from TabWidget)
// Help menu
// (come from MainWindow)
};
static const action_data cms_create_csr_action_data = {
"file_new_certificate_signing_request",
i18n("New S/MIME Certification Request..."),
i18n("Create a new S/MIME certificate signing request (CSR)"),
"view-certificate-add",
nullptr,
nullptr,
{},
};
static const std::vector<action_data> cms_action_data = {
// Certificate menu
{
"certificates_trust_root",
i18n("Trust Root Certificate"),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"certificates_distrust_root",
i18n("Distrust Root Certificate"),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"certificates_dump_certificate",
i18n("Technical Details"),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
// Tools menu
{
"tools_refresh_x509_certificates",
i18n("Refresh S/MIME Certificates"),
QString(),
"view-refresh",
nullptr,
nullptr,
QString(),
},
{
"crl_clear_crl_cache",
i18n("Clear CRL Cache"),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"crl_dump_crl_cache",
i18n("Dump CRL Cache"),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
{
"crl_import_crl",
i18n("Import CRL From File..."),
QString(),
nullptr,
nullptr,
nullptr,
QString(),
},
};
std::vector<action_data> action_data = common_and_openpgp_action_data;
if (const Kleo::Settings settings{}; settings.cmsEnabled()) {
if (settings.cmsCertificateCreationAllowed()) {
action_data.push_back(cms_create_csr_action_data);
}
action_data.reserve(action_data.size() + cms_action_data.size());
ranges::copy(cms_action_data, std::back_inserter(action_data));
}
make_actions_from_data(action_data, coll);
if (QAction *action = coll->action(QStringLiteral("view_stop_operations"))) {
connect(this, &KeyListController::commandsExecuting, action, &QAction::setEnabled);
}
// ### somehow make this better...
registerActionForCommand<NewOpenPGPCertificateCommand>(coll->action(QStringLiteral("file_new_certificate")));
registerActionForCommand<NewCertificateSigningRequestCommand>(coll->action(QStringLiteral("file_new_certificate_signing_request")));
//---
registerActionForCommand<LookupCertificatesCommand>(coll->action(QStringLiteral("file_lookup_certificates")));
registerActionForCommand<ImportCertificateFromFileCommand>(coll->action(QStringLiteral("file_import_certificates")));
//---
registerActionForCommand<ExportCertificateCommand>(coll->action(QStringLiteral("file_export_certificates")));
registerActionForCommand<ExportSecretKeyCommand>(coll->action(QStringLiteral("file_export_secret_keys")));
registerActionForCommand<ExportPaperKeyCommand>(coll->action(QStringLiteral("file_export_paper_key")));
registerActionForCommand<ExportOpenPGPCertsToServerCommand>(coll->action(QStringLiteral("file_export_certificates_to_server")));
#ifdef MAILAKONADI_ENABLED
registerActionForCommand<ExportOpenPGPCertToProviderCommand>(coll->action(QStringLiteral("file_export_certificate_to_provider")));
#endif // MAILAKONADI_ENABLED
registerActionForCommand<CertificateToCardCommand>(coll->action(QStringLiteral("certificate_to_card")));
//---
registerActionForCommand<DecryptVerifyFilesCommand>(coll->action(QStringLiteral("file_decrypt_verify_files")));
registerActionForCommand<SignEncryptFilesCommand>(coll->action(QStringLiteral("file_sign_encrypt_files")));
registerActionForCommand<SignEncryptFolderCommand>(coll->action(QStringLiteral("file_sign_encrypt_folder")));
//---
registerActionForCommand<ChecksumCreateFilesCommand>(coll->action(QStringLiteral("file_checksum_create_files")));
registerActionForCommand<ChecksumVerifyFilesCommand>(coll->action(QStringLiteral("file_checksum_verify_files")));
registerActionForCommand<ReloadKeysCommand>(coll->action(QStringLiteral("view_redisplay")));
// coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data()
registerActionForCommand<DetailsCommand>(coll->action(QStringLiteral("view_certificate_details")));
registerActionForCommand<ChangeOwnerTrustCommand>(coll->action(QStringLiteral("certificates_change_owner_trust")));
registerActionForCommand<TrustRootCommand>(coll->action(QStringLiteral("certificates_trust_root")));
registerActionForCommand<DistrustRootCommand>(coll->action(QStringLiteral("certificates_distrust_root")));
//---
registerActionForCommand<CertifyCertificateCommand>(coll->action(QStringLiteral("certificates_certify_certificate")));
if (RevokeCertificationCommand::isSupported()) {
registerActionForCommand<RevokeCertificationCommand>(coll->action(QStringLiteral("certificates_revoke_certification")));
}
//---
registerActionForCommand<ChangeExpiryCommand>(coll->action(QStringLiteral("certificates_change_expiry")));
registerActionForCommand<ChangePassphraseCommand>(coll->action(QStringLiteral("certificates_change_passphrase")));
registerActionForCommand<AddUserIDCommand>(coll->action(QStringLiteral("certificates_add_userid")));
registerActionForCommand<CreateGroupCommand>(coll->action(QStringLiteral("certificates_create_group")));
//---
registerActionForCommand<RevokeKeyCommand>(coll->action(QStringLiteral("certificates_revoke")));
registerActionForCommand<DeleteCertificatesCommand>(coll->action(QStringLiteral("certificates_delete")));
//---
registerActionForCommand<RefreshCertificatesCommand>(coll->action(QStringLiteral("certificates_refresh")));
//---
registerActionForCommand<DumpCertificateCommand>(coll->action(QStringLiteral("certificates_dump_certificate")));
registerActionForCommand<RefreshX509CertsCommand>(coll->action(QStringLiteral("tools_refresh_x509_certificates")));
registerActionForCommand<RefreshOpenPGPCertsCommand>(coll->action(QStringLiteral("tools_refresh_openpgp_certificates")));
//---
registerActionForCommand<ImportCrlCommand>(coll->action(QStringLiteral("crl_import_crl")));
//---
registerActionForCommand<ClearCrlCacheCommand>(coll->action(QStringLiteral("crl_clear_crl_cache")));
registerActionForCommand<DumpCrlCacheCommand>(coll->action(QStringLiteral("crl_dump_crl_cache")));
enableDisableActions(nullptr);
}
void KeyListController::registerAction(QAction *action, Command::Restrictions restrictions, Command *(*create)(QAbstractItemView *, KeyListController *))
{
if (!action) {
return;
}
Q_ASSERT(!action->isCheckable()); // can be added later, for now, disallow
const Private::action_item ai = {action, restrictions, create};
connect(action, &QAction::triggered, this, [this, action]() {
d->slotActionTriggered(action);
});
d->actions.push_back(ai);
}
void KeyListController::registerCommand(Command *cmd)
{
if (!cmd || ranges::binary_search(d->commands, cmd)) {
return;
}
d->addCommand(cmd);
qCDebug(KLEOPATRA_LOG) << (void *)cmd;
if (d->commands.size() == 1) {
Q_EMIT commandsExecuting(true);
}
}
bool KeyListController::hasRunningCommands() const
{
return !d->commands.empty();
}
bool KeyListController::shutdownWarningRequired() const
{
return ranges::any_of(d->commands, std::mem_fn(&Command::warnWhenRunningAtShutdown));
}
// slot
void KeyListController::cancelCommands()
{
ranges::for_each(d->commands, std::mem_fn(&Command::cancel));
}
void KeyListController::Private::connectView(QAbstractItemView *view)
{
connect(view, &QObject::destroyed, q, [this](QObject *obj) {
slotDestroyed(obj);
});
connect(view, &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) {
slotDoubleClicked(index);
});
connect(view, &QAbstractItemView::activated, q, [this](const QModelIndex &index) {
slotActivated(index);
});
connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this](const QItemSelection &oldSel, const QItemSelection &newSel) {
slotSelectionChanged(oldSel, newSel);
});
view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) {
slotContextMenu(pos);
});
}
void KeyListController::Private::connectCommand(Command *cmd)
{
if (!cmd) {
return;
}
connect(cmd, &QObject::destroyed, q, [this](QObject *obj) {
slotDestroyed(obj);
});
connect(cmd, &Command::finished, q, [this] {
slotCommandFinished();
});
// connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) );
connect(cmd, &Command::progress, q, &KeyListController::progress);
}
void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx)
{
QAbstractItemView *const view = qobject_cast<QAbstractItemView *>(q->sender());
if (!view || !ranges::binary_search(views, view)) {
return;
}
if (const auto *const keyListModel = dynamic_cast<KeyListModelInterface *>(view->model())) {
DetailsCommand *const c = new DetailsCommand{keyListModel->key(idx)};
c->setParentWidget(parentWidget ? parentWidget : view);
c->start();
}
}
void KeyListController::Private::slotActivated(const QModelIndex &idx)
{
Q_UNUSED(idx)
QAbstractItemView *const view = qobject_cast<QAbstractItemView *>(q->sender());
if (!view || !ranges::binary_search(views, view)) {
return;
}
}
void KeyListController::Private::slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_)
{
Q_UNUSED(old)
Q_UNUSED(new_)
const QItemSelectionModel *const sm = qobject_cast<QItemSelectionModel *>(q->sender());
if (!sm) {
return;
}
q->enableDisableActions(sm);
}
void KeyListController::Private::slotContextMenu(const QPoint &p)
{
QAbstractItemView *const view = qobject_cast<QAbstractItemView *>(q->sender());
if (view && ranges::binary_search(views, view)) {
Q_EMIT q->contextMenuRequested(view, view->viewport()->mapToGlobal(p));
} else {
qCDebug(KLEOPATRA_LOG) << "sender is not a QAbstractItemView*!";
}
}
void KeyListController::Private::slotCommandFinished()
{
Command *const cmd = qobject_cast<Command *>(q->sender());
if (!cmd || !ranges::binary_search(commands, cmd)) {
return;
}
qCDebug(KLEOPATRA_LOG) << (void *)cmd;
if (commands.size() == 1) {
Q_EMIT q->commandsExecuting(false);
}
}
void KeyListController::enableDisableActions(const QItemSelectionModel *sm) const
{
const Command::Restrictions restrictionsMask = d->calculateRestrictionsMask(sm);
for (const Private::action_item &ai : std::as_const(d->actions))
if (ai.action) {
ai.action->setEnabled(ai.restrictions == (ai.restrictions & restrictionsMask));
}
}
static bool all_secret_are_not_owner_trust_ultimate(const std::vector<Key> &keys)
{
for (const Key &key : keys)
if (key.hasSecret() && key.ownerTrust() == Key::Ultimate) {
return false;
}
return true;
}
Command::Restrictions find_root_restrictions(const std::vector<Key> &keys)
{
bool trusted = false, untrusted = false;
for (const Key &key : keys)
if (key.isRoot())
if (key.userID(0).validity() == UserID::Ultimate) {
trusted = true;
} else {
untrusted = true;
}
else {
return Command::NoRestriction;
}
if (trusted)
if (untrusted) {
return Command::NoRestriction;
} else {
return Command::MustBeTrustedRoot;
}
else if (untrusted) {
return Command::MustBeUntrustedRoot;
} else {
return Command::NoRestriction;
}
}
static bool secretSubkeyDataAvailable(const Subkey &subkey)
{
return subkey.isSecret() && !subkey.isCardKey();
}
Command::Restrictions KeyListController::Private::calculateRestrictionsMask(const QItemSelectionModel *sm)
{
if (!sm) {
return Command::NoRestriction;
}
const KeyListModelInterface *const m = dynamic_cast<const KeyListModelInterface *>(sm->model());
if (!m) {
return Command::NoRestriction;
}
const std::vector<Key> keys = m->keys(sm->selectedRows());
if (keys.empty()) {
return Command::NoRestriction;
}
Command::Restrictions result = Command::NeedSelection;
if (keys.size() == 1) {
result |= Command::OnlyOneKey;
}
// we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available
const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) {
return k.subkey(0).isSecret();
};
if (ranges::all_of(keys, primaryKeyCanBeUsedForSecretKeyOperations)) {
result |= Command::NeedSecretKey;
}
if (ranges::all_of(keys, [](const auto &k) {
return ranges::any_of(k.subkeys(), &secretSubkeyDataAvailable);
})) {
result |= Command::NeedSecretSubkeyData;
}
if ((result & Command::NeedSecretSubkeyData) && ranges::all_of(keys, [](const auto &k) {
return secretSubkeyDataAvailable(k.subkey(0));
})) {
result |= Command::NeedSecretPrimaryKeyData;
}
if (ranges::all_of(keys, [](const Key &key) {
return key.protocol() == OpenPGP;
})) {
result |= Command::MustBeOpenPGP;
} else if (ranges::all_of(keys, [](const Key &key) {
return key.protocol() == CMS;
})) {
result |= Command::MustBeCMS;
}
if (ranges::all_of(keys, [](const auto &key) {
return !key.isBad();
})) {
result |= Command::MustBeValid;
}
if (all_secret_are_not_owner_trust_ultimate(keys)) {
result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate;
}
result |= find_root_restrictions(keys);
if (const ReaderStatus *rs = ReaderStatus::instance()) {
if (!rs->firstCardWithNullPin().empty()) {
result |= Command::AnyCardHasNullPin;
}
}
{
if (keys.size() > 0) {
bool hasSignCertify = false;
bool hasEncrypt = false;
bool hasAuthenticate = false;
bool invalid = false;
for (const auto &subkey : keys[0].subkeys()) {
if (subkey.isCardKey()) {
invalid = true;
break;
}
if (subkey.canCertify() && subkey.canSign()) {
if (hasSignCertify) {
invalid = true;
break;
} else {
hasSignCertify = true;
}
} else if (subkey.canEncrypt()) {
if (hasEncrypt) {
invalid = true;
break;
} else {
hasEncrypt = true;
}
} else if (subkey.canAuthenticate()) {
if (hasAuthenticate) {
invalid = true;
break;
} else {
hasAuthenticate = true;
}
} else {
invalid = true;
break;
}
}
if (hasSignCertify && hasEncrypt && !invalid) {
result |= Command::SuitableForCard;
}
}
}
return result;
}
void KeyListController::Private::slotActionTriggered(QAction *sender)
{
const auto it = ranges::find_if(actions, [sender](const action_item &item) {
return item.action == sender;
});
if (it != actions.end())
if (Command *const c = it->createCommand(this->currentView, q)) {
if (parentWidget) {
c->setParentWidget(parentWidget);
}
c->start();
} else
qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \"" << qPrintable(sender->objectName()) << "\"";
else {
qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(sender->objectName());
}
}
int KeyListController::Private::toolTipOptions() const
{
using namespace Kleo::Formatting;
static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage;
static const int ownerFlags = Subject | UserIDs | OwnerTrust;
static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint;
const TooltipPreferences prefs;
int flags = KeyID;
flags |= prefs.showValidity() ? validityFlags : 0;
flags |= prefs.showOwnerInformation() ? ownerFlags : 0;
flags |= prefs.showCertificateDetails() ? detailsFlags : 0;
return flags;
}
void KeyListController::updateConfig()
{
const int opts = d->toolTipOptions();
if (d->flatModel) {
d->flatModel->setToolTipOptions(opts);
}
if (d->hierarchicalModel) {
d->hierarchicalModel->setToolTipOptions(opts);
}
}
#include "moc_keylistcontroller.cpp"
diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp
index 7692bbb88..b1e14f815 100644
--- a/src/view/keytreeview.cpp
+++ b/src/view/keytreeview.cpp
@@ -1,688 +1,680 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/keytreeview.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "keytreeview.h"
#include "searchbar.h"
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeyRearrangeColumnsProxyModel>
#include <Libkleo/Predicates>
#include <Libkleo/TreeView>
#include "utils/headerview.h"
#include "utils/tags.h"
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/Stl_Util>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
#include <QAction>
#include <QClipboard>
#include <QContextMenuEvent>
#include <QEvent>
#include <QGuiApplication>
#include <QHeaderView>
#include <QItemSelection>
#include <QItemSelectionModel>
#include <QLayout>
#include <QList>
#include <QMenu>
#include <QTimer>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KStandardAction>
#include <qnamespace.h>
static int tagsColumn;
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
class TreeViewInternal : public Kleo::TreeView
{
public:
explicit TreeViewInternal(QWidget *parent = nullptr)
: Kleo::TreeView{parent}
{
}
QSize minimumSizeHint() const override
{
const QSize min = QTreeView::minimumSizeHint();
return QSize(min.width(), min.height() + 5 * fontMetrics().height());
}
protected:
void focusInEvent(QFocusEvent *event) override
{
QTreeView::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &TreeViewInternal::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
private:
QList<QAction *> mColumnActions;
};
const KeyListModelInterface *keyListModel(const QTreeView &view)
{
const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(view.model());
Q_ASSERT(klmi);
return klmi;
}
} // anon namespace
KeyTreeView::KeyTreeView(QWidget *parent)
: QWidget(parent)
, m_proxy(new KeyListSortFilterProxyModel(this))
, m_additionalProxy(nullptr)
, m_view(new TreeViewInternal(this))
, m_flatModel(nullptr)
, m_hierarchicalModel(nullptr)
, m_stringFilter()
, m_keyFilter()
, m_isHierarchical(true)
, m_showDefaultContextMenu(true)
{
init();
}
KeyTreeView::KeyTreeView(const KeyTreeView &other)
: QWidget(nullptr)
, m_proxy(new KeyListSortFilterProxyModel(this))
, m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr)
, m_view(new TreeViewInternal(this))
, m_flatModel(other.m_flatModel)
, m_hierarchicalModel(other.m_hierarchicalModel)
, m_stringFilter(other.m_stringFilter)
, m_keyFilter(other.m_keyFilter)
, m_group(other.m_group)
, m_isHierarchical(other.m_isHierarchical)
, m_showDefaultContextMenu(other.m_showDefaultContextMenu)
{
init();
setColumnSizes(other.columnSizes());
setSortColumn(other.sortColumn(), other.sortOrder());
}
KeyTreeView::KeyTreeView(const QString &text,
const std::shared_ptr<KeyFilter> &kf,
AbstractKeyListSortFilterProxyModel *proxy,
QWidget *parent,
const KConfigGroup &group,
Options options)
: QWidget(parent)
, m_proxy(new KeyListSortFilterProxyModel(this))
, m_additionalProxy(proxy)
, m_view(new TreeViewInternal(this))
, m_flatModel(nullptr)
, m_hierarchicalModel(nullptr)
, m_stringFilter(text)
, m_keyFilter(kf)
, m_group(group)
, m_isHierarchical(true)
, m_onceResized(false)
, m_showDefaultContextMenu(!(options & Option::NoDefaultContextMenu))
{
init();
}
void KeyTreeView::setColumnSizes(const std::vector<int> &sizes)
{
if (sizes.empty()) {
return;
}
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
hv->setSectionSizes(sizes);
}
}
void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder)
{
Q_ASSERT(m_view);
m_view->sortByColumn(sortColumn, sortOrder);
}
int KeyTreeView::sortColumn() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
return m_view->header()->sortIndicatorSection();
}
Qt::SortOrder KeyTreeView::sortOrder() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
return m_view->header()->sortIndicatorOrder();
}
std::vector<int> KeyTreeView::columnSizes() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
return hv->sectionSizes();
} else {
return std::vector<int>();
}
}
void KeyTreeView::restoreLayout(const KConfigGroup &group)
{
if (!group.isValid() || !m_view->restoreColumnLayout(group.name())) {
// if config is empty then use default settings
// The numbers have to be in line with the order in
// setsSourceColumns above
m_view->hideColumn(5);
for (int i = 7; i < m_view->model()->columnCount(); ++i) {
m_view->hideColumn(i);
}
if (KeyCache::instance()->initialized()) {
QTimer::singleShot(0, this, &KeyTreeView::initializeColumnSizes);
}
} else {
m_onceResized = true;
}
}
void KeyTreeView::init()
{
Q_SET_OBJECT_NAME(m_proxy);
Q_SET_OBJECT_NAME(m_view);
if (m_group.isValid()) {
// Reopen as non const
KConfig *conf = m_group.config();
m_group = conf->group(m_group.name());
}
if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) {
Q_SET_OBJECT_NAME(m_additionalProxy);
}
QLayout *layout = new QVBoxLayout(this);
Q_SET_OBJECT_NAME(layout);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_view);
auto headerView = new HeaderView(Qt::Horizontal);
Q_SET_OBJECT_NAME(headerView);
headerView->installEventFilter(m_view);
headerView->setSectionsMovable(true);
m_view->setHeader(headerView);
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_view->setAllColumnsShowFocus(false);
m_view->setSortingEnabled(true);
m_view->setAccessibleName(i18n("Certificates"));
m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
// we show details on double-click
m_view->setExpandsOnDoubleClick(false);
if (m_showDefaultContextMenu) {
m_view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_view, &KeyTreeView::customContextMenuRequested, this, [this](const auto &pos) {
auto menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose, true);
menu->addAction(KStandardAction::copy(
this,
[this]() {
- QGuiApplication::clipboard()->setText(m_view->currentIndex().data(KeyList::ClipboardRole).toString());
+ QGuiApplication::clipboard()->setText(m_view->currentIndex().data(Kleo::ClipboardRole).toString());
},
this));
menu->popup(m_view->mapToGlobal(pos));
});
}
if (model()) {
if (m_additionalProxy) {
m_additionalProxy->setSourceModel(model());
} else {
m_proxy->setSourceModel(model());
}
}
if (m_additionalProxy) {
m_proxy->setSourceModel(m_additionalProxy);
if (!m_additionalProxy->parent()) {
m_additionalProxy->setParent(this);
}
}
m_proxy->setFilterRegularExpression(QRegularExpression::escape(m_stringFilter.trimmed()));
m_proxy->setKeyFilter(m_keyFilter);
m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
auto rearangingModel = new KeyRearrangeColumnsProxyModel(this);
rearangingModel->setSourceModel(m_proxy);
QList<int> columns = {
KeyList::PrettyName,
KeyList::PrettyEMail,
KeyList::Validity,
KeyList::ValidFrom,
KeyList::ValidUntil,
KeyList::TechnicalDetails,
KeyList::KeyID,
KeyList::Fingerprint,
KeyList::OwnerTrust,
KeyList::Origin,
KeyList::LastUpdate,
KeyList::Issuer,
KeyList::SerialNumber,
KeyList::Remarks,
KeyList::Algorithm,
KeyList::Keygrip,
};
tagsColumn = columns.indexOf(KeyList::Remarks);
rearangingModel->setSourceColumns(columns);
m_view->setModel(rearangingModel);
/* Handle expansion state */
if (m_group.isValid()) {
m_expandedKeys = m_group.readEntry("Expanded", QStringList());
}
connect(m_view, &QTreeView::expanded, this, [this](const QModelIndex &index) {
if (!index.isValid()) {
return;
}
const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
return;
}
const auto fpr = QString::fromLatin1(key.primaryFingerprint());
if (m_expandedKeys.contains(fpr)) {
return;
}
m_expandedKeys << fpr;
if (m_group.isValid()) {
m_group.writeEntry("Expanded", m_expandedKeys);
}
});
connect(m_view, &QTreeView::collapsed, this, [this](const QModelIndex &index) {
if (!index.isValid()) {
return;
}
const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
return;
}
m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint()));
if (m_group.isValid()) {
m_group.writeEntry("Expanded", m_expandedKeys);
}
});
updateModelConnections(nullptr, model());
}
void KeyTreeView::restoreExpandState()
{
if (!KeyCache::instance()->initialized()) {
qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting.";
return;
}
for (const auto &fpr : std::as_const(m_expandedKeys)) {
const KeyListModelInterface *const km = keyListModel(*m_view);
if (!km) {
qCWarning(KLEOPATRA_LOG) << "invalid model";
return;
}
const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData());
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache";
m_expandedKeys.removeAll(fpr);
return;
}
const auto idx = km->index(key);
if (!idx.isValid()) {
qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model";
m_expandedKeys.removeAll(fpr);
return;
}
m_view->expand(idx);
}
}
void KeyTreeView::setUpTagKeys()
{
const auto tagKeys = Tags::tagKeys();
if (m_hierarchicalModel) {
m_hierarchicalModel->setRemarkKeys(tagKeys);
}
if (m_flatModel) {
m_flatModel->setRemarkKeys(tagKeys);
}
}
KeyTreeView::~KeyTreeView() = default;
static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm)
{
Q_ASSERT(pm);
while (auto const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) {
pm = sm;
}
return pm;
}
void KeyTreeView::updateModelConnections(AbstractKeyListModel *oldModel, AbstractKeyListModel *newModel)
{
if (oldModel == newModel) {
return;
}
if (oldModel) {
disconnect(oldModel, &QAbstractItemModel::modelAboutToBeReset, this, &KeyTreeView::saveStateBeforeModelChange);
disconnect(oldModel, &QAbstractItemModel::modelReset, this, &KeyTreeView::restoreStateAfterModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &KeyTreeView::saveStateBeforeModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsInserted, this, &KeyTreeView::restoreStateAfterModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &KeyTreeView::saveStateBeforeModelChange);
disconnect(oldModel, &QAbstractItemModel::rowsRemoved, this, &KeyTreeView::restoreStateAfterModelChange);
}
if (newModel) {
connect(newModel, &QAbstractItemModel::modelAboutToBeReset, this, &KeyTreeView::saveStateBeforeModelChange);
connect(newModel, &QAbstractItemModel::modelReset, this, &KeyTreeView::restoreStateAfterModelChange);
connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &KeyTreeView::saveStateBeforeModelChange);
connect(newModel, &QAbstractItemModel::rowsInserted, this, &KeyTreeView::restoreStateAfterModelChange);
connect(newModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &KeyTreeView::saveStateBeforeModelChange);
connect(newModel, &QAbstractItemModel::rowsRemoved, this, &KeyTreeView::restoreStateAfterModelChange);
}
}
void KeyTreeView::setFlatModel(AbstractKeyListModel *model)
{
if (model == m_flatModel) {
return;
}
auto oldModel = m_flatModel;
m_flatModel = model;
if (!m_isHierarchical)
// TODO: this fails when called after setHierarchicalView( false )...
{
find_last_proxy(m_proxy)->setSourceModel(model);
updateModelConnections(oldModel, model);
}
}
void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == m_hierarchicalModel) {
return;
}
auto oldModel = m_hierarchicalModel;
m_hierarchicalModel = model;
if (m_isHierarchical) {
find_last_proxy(m_proxy)->setSourceModel(model);
updateModelConnections(oldModel, model);
m_view->expandAll();
for (int column = 0; column < m_view->header()->count(); ++column) {
m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column)));
}
}
}
void KeyTreeView::setStringFilter(const QString &filter)
{
if (filter == m_stringFilter) {
return;
}
m_stringFilter = filter;
m_proxy->setFilterRegularExpression(QRegularExpression::escape(filter.trimmed()));
Q_EMIT stringFilterChanged(filter);
}
void KeyTreeView::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) {
return;
}
m_keyFilter = filter;
m_proxy->setKeyFilter(filter);
Q_EMIT keyFilterChanged(filter);
}
namespace
{
QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const QTreeView &view)
{
const QModelIndexList indexes = keyListModel(view)->indexes(keys);
return std::accumulate(indexes.cbegin(), indexes.cend(), QItemSelection(), [](QItemSelection selection, const QModelIndex &index) {
if (index.isValid()) {
selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
}
return selection;
});
}
}
void KeyTreeView::selectKeys(const std::vector<Key> &keys)
{
m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
std::vector<Key> KeyTreeView::selectedKeys() const
{
return keyListModel(*m_view)->keys(m_view->selectionModel()->selectedRows());
}
void KeyTreeView::setHierarchicalView(bool on)
{
if (on == m_isHierarchical) {
return;
}
if (on && !hierarchicalModel()) {
qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set";
return;
}
if (!on && !flatModel()) {
qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set";
return;
}
const std::vector<Key> selectedKeys = this->selectedKeys();
const Key currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
auto oldModel = model();
m_isHierarchical = on;
find_last_proxy(m_proxy)->setSourceModel(model());
updateModelConnections(oldModel, model());
if (on) {
m_view->expandAll();
}
selectKeys(selectedKeys);
if (!currentKey.isNull()) {
const QModelIndex currentIndex = keyListModel(*m_view)->index(currentKey);
if (currentIndex.isValid()) {
m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
m_view->scrollTo(currentIndex);
}
}
m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
Q_EMIT hierarchicalChanged(on);
}
void KeyTreeView::setKeys(const std::vector<Key> &keys, const std::vector<Key::Origin> &extraOrigins)
{
std::vector<Key> sorted = keys;
if (extraOrigins.empty()) {
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
}
m_keys = sorted;
if (m_flatModel) {
m_flatModel->setKeys(sorted, extraOrigins);
}
if (m_hierarchicalModel) {
m_hierarchicalModel->setKeys(sorted, extraOrigins);
}
}
void KeyTreeView::addKeysImpl(const std::vector<Key> &keys, bool select)
{
if (keys.empty()) {
return;
}
if (m_keys.empty()) {
setKeys(keys);
return;
}
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
std::vector<Key> newKeys = _detail::union_by_fpr(sorted, m_keys);
m_keys.swap(newKeys);
if (m_flatModel) {
m_flatModel->addKeys(sorted);
}
if (m_hierarchicalModel) {
m_hierarchicalModel->addKeys(sorted);
}
if (select) {
selectKeys(sorted);
}
}
void KeyTreeView::addKeysSelected(const std::vector<Key> &keys)
{
addKeysImpl(keys, true);
}
void KeyTreeView::addKeysUnselected(const std::vector<Key> &keys)
{
addKeysImpl(keys, false);
}
void KeyTreeView::removeKeys(const std::vector<Key> &keys)
{
if (keys.empty()) {
return;
}
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
std::vector<Key> newKeys;
newKeys.reserve(m_keys.size());
std::set_difference(m_keys.begin(), m_keys.end(), sorted.begin(), sorted.end(), std::back_inserter(newKeys), _detail::ByFingerprint<std::less>());
m_keys.swap(newKeys);
if (m_flatModel) {
std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) {
m_flatModel->removeKey(key);
});
}
if (m_hierarchicalModel) {
std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) {
m_hierarchicalModel->removeKey(key);
});
}
}
void KeyTreeView::disconnectSearchBar()
{
for (const auto &connection : m_connections) {
disconnect(connection);
}
m_connections.clear();
}
bool KeyTreeView::connectSearchBar(const SearchBar *bar)
{
m_connections.reserve(4);
m_connections.push_back(connect(this, &KeyTreeView::stringFilterChanged, bar, &SearchBar::setStringFilter));
m_connections.push_back(connect(bar, &SearchBar::stringFilterChanged, this, &KeyTreeView::setStringFilter));
m_connections.push_back(connect(this, &KeyTreeView::keyFilterChanged, bar, &SearchBar::setKeyFilter));
m_connections.push_back(connect(bar, &SearchBar::keyFilterChanged, this, &KeyTreeView::setKeyFilter));
return std::all_of(m_connections.cbegin(), m_connections.cend(), [](const QMetaObject::Connection &conn) {
return conn;
});
}
void KeyTreeView::initializeColumnSizes()
{
if (m_onceResized || m_view->model()->rowCount() == 0) {
return;
}
m_onceResized = true;
m_view->setColumnWidth(KeyList::PrettyName, 260);
m_view->setColumnWidth(KeyList::PrettyEMail, 260);
for (int i = 2; i < m_view->model()->columnCount(); ++i) {
m_view->resizeColumnToContents(i);
}
}
void KeyTreeView::saveStateBeforeModelChange()
{
m_currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
m_selectedKeys = selectedKeys();
}
void KeyTreeView::restoreStateAfterModelChange()
{
restoreExpandState();
selectKeys(m_selectedKeys);
if (!m_currentKey.isNull()) {
const QModelIndex currentIndex = keyListModel(*m_view)->index(m_currentKey);
if (currentIndex.isValid()) {
m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
m_view->scrollTo(currentIndex);
}
}
setUpTagKeys();
initializeColumnSizes();
}
-void KeyTreeView::keyPressEvent(QKeyEvent *event)
-{
- if (event == QKeySequence::Copy) {
- QGuiApplication::clipboard()->setText(view()->currentIndex().data(KeyList::ClipboardRole).toString());
- event->accept();
- }
-}
-
#include "moc_keytreeview.cpp"
diff --git a/src/view/keytreeview.h b/src/view/keytreeview.h
index 68306870f..9a9c82540 100644
--- a/src/view/keytreeview.h
+++ b/src/view/keytreeview.h
@@ -1,180 +1,179 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/keytreeview.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QWidget>
#include <QString>
#include <QStringList>
#include <gpgme++/key.h>
#include <memory>
#include <vector>
#include <KConfigGroup>
#include <Libkleo/TreeView>
class QTreeView;
namespace Kleo
{
class KeyFilter;
class AbstractKeyListModel;
class AbstractKeyListSortFilterProxyModel;
class KeyListSortFilterProxyModel;
class SearchBar;
class KeyTreeView : public QWidget
{
Q_OBJECT
Q_FLAGS(Options)
public:
enum Option {
Default = 0x0,
NoDefaultContextMenu = 0x1,
};
Q_DECLARE_FLAGS(Options, Option)
explicit KeyTreeView(QWidget *parent = nullptr);
KeyTreeView(const QString &stringFilter,
const std::shared_ptr<KeyFilter> &keyFilter,
AbstractKeyListSortFilterProxyModel *additionalProxy,
QWidget *parent,
const KConfigGroup &group,
Options options = Option::Default);
~KeyTreeView() override;
TreeView *view() const
{
return m_view;
}
AbstractKeyListModel *model() const
{
return m_isHierarchical ? hierarchicalModel() : flatModel();
}
AbstractKeyListModel *flatModel() const
{
return m_flatModel;
}
AbstractKeyListModel *hierarchicalModel() const
{
return m_hierarchicalModel;
}
void setFlatModel(AbstractKeyListModel *model);
void setHierarchicalModel(AbstractKeyListModel *model);
// extraOrigins contains additional origin information for the keys. It must be in the same order as the keys themselves.
// For this reason, setKeys will NOT perform any sorting and filtering if extraOrigins is not empty.
void setKeys(const std::vector<GpgME::Key> &keys, const std::vector<GpgME::Key::Origin> &extraOrigins = {});
const std::vector<GpgME::Key> &keys() const
{
return m_keys;
}
void selectKeys(const std::vector<GpgME::Key> &keys);
std::vector<GpgME::Key> selectedKeys() const;
void addKeysUnselected(const std::vector<GpgME::Key> &keys);
void addKeysSelected(const std::vector<GpgME::Key> &keys);
void removeKeys(const std::vector<GpgME::Key> &keys);
#if 0
void setToolTipOptions(int options);
int toolTipOptions() const;
#endif
QString stringFilter() const
{
return m_stringFilter;
}
const std::shared_ptr<KeyFilter> &keyFilter() const
{
return m_keyFilter;
}
bool isHierarchicalView() const
{
return m_isHierarchical;
}
void setColumnSizes(const std::vector<int> &sizes);
std::vector<int> columnSizes() const;
void setSortColumn(int sortColumn, Qt::SortOrder sortOrder);
int sortColumn() const;
Qt::SortOrder sortOrder() const;
virtual KeyTreeView *clone() const
{
return new KeyTreeView(*this);
}
void disconnectSearchBar();
bool connectSearchBar(const SearchBar *bar);
void restoreLayout(const KConfigGroup &group);
public Q_SLOTS:
virtual void setStringFilter(const QString &text);
virtual void setKeyFilter(const std::shared_ptr<Kleo::KeyFilter> &filter);
virtual void setHierarchicalView(bool on);
Q_SIGNALS:
void stringFilterChanged(const QString &filter);
void keyFilterChanged(const std::shared_ptr<Kleo::KeyFilter> &filter);
void hierarchicalChanged(bool on);
protected:
KeyTreeView(const KeyTreeView &);
- void keyPressEvent(QKeyEvent *event) override;
private:
void init();
void initializeColumnSizes();
void addKeysImpl(const std::vector<GpgME::Key> &, bool);
void restoreExpandState();
void setUpTagKeys();
void updateModelConnections(AbstractKeyListModel *oldModel, AbstractKeyListModel *newModel);
void saveStateBeforeModelChange();
void restoreStateAfterModelChange();
private:
std::vector<GpgME::Key> m_keys;
KeyListSortFilterProxyModel *m_proxy;
AbstractKeyListSortFilterProxyModel *m_additionalProxy;
TreeView *m_view;
AbstractKeyListModel *m_flatModel;
AbstractKeyListModel *m_hierarchicalModel;
QString m_stringFilter;
std::shared_ptr<KeyFilter> m_keyFilter;
QStringList m_expandedKeys;
std::vector<GpgME::Key> m_selectedKeys;
GpgME::Key m_currentKey;
std::vector<QMetaObject::Connection> m_connections;
KConfigGroup m_group;
bool m_isHierarchical : 1;
bool m_onceResized : 1;
bool m_showDefaultContextMenu : 1;
};
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 17, 12:16 AM (1 h, 39 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e5/ed/d56e1034867feabc625132a32871
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment