Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F18824770
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
22 KB
Subscribers
None
View Options
diff --git a/src/dialogs/editgroupdialog.cpp b/src/dialogs/editgroupdialog.cpp
index 5072a0e50..c7ccf67b1 100644
--- a/src/dialogs/editgroupdialog.cpp
+++ b/src/dialogs/editgroupdialog.cpp
@@ -1,547 +1,547 @@
/*
dialogs/editgroupdialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "editgroupdialog.h"
#include "commands/detailscommand.h"
#include "utils/gui-helper.h"
#include "view/keytreeview.h"
#include <settings.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Compat>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyHelpers>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <KColorScheme>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QApplication>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QItemSelectionModel>
#include <QLabel>
#include <QLineEdit>
#include <QPalette>
#include <QPushButton>
#include <QTreeView>
#include <QVBoxLayout>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
auto createOpenPGPOnlyKeyFilter()
{
auto filter = std::make_shared<DefaultKeyFilter>();
filter->setIsOpenPGP(DefaultKeyFilter::Set);
return filter;
}
}
namespace
{
class FiltersProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
using QSortFilterProxyModel::QSortFilterProxyModel;
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
{
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
const auto matchContexts = qvariant_cast<KeyFilter::MatchContexts>(sourceModel()->data(index, KeyFilterManager::FilterMatchContextsRole));
return matchContexts & KeyFilter::Filtering;
}
};
}
class WarnNonEncryptionKeysProxyModel : public Kleo::AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
enum Mode {
Warn,
Disable,
};
WarnNonEncryptionKeysProxyModel(Mode mode, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
, m_mode(mode)
{
}
WarnNonEncryptionKeysProxyModel *clone() const override
{
return new WarnNonEncryptionKeysProxyModel(m_mode, parent());
}
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return {};
}
const auto sourceIndex = sourceModel()->index(index.row(), index.column());
const auto &key = sourceIndex.data(KeyList::KeyRole).value<Key>();
if (!Kleo::canBeUsedForEncryption(key)) {
if (role == Qt::DecorationRole && index.column() == KeyList::Columns::Validity) {
return QIcon::fromTheme(QStringLiteral("data-error"));
}
if (role == Qt::DisplayRole && index.column() == KeyList::Columns::Validity) {
return i18nc("@info as in 'this certificate is unusable'", "unusable");
}
if (role == Qt::ToolTipRole) {
return i18nc("@info:tooltip", "This certificate cannot be used for encryption.");
}
if (role == Qt::BackgroundRole || role == Qt::ForegroundRole) {
return {};
}
}
return sourceIndex.data(role);
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
auto originalFlags = index.model()->QAbstractItemModel::flags(index);
const auto key = index.data(KeyList::KeyRole).value<Key>();
if (m_mode == Warn || Kleo::canBeUsedForEncryption(key)) {
return originalFlags;
} else {
return (originalFlags & ~Qt::ItemIsEnabled);
}
return {};
}
private:
Mode m_mode;
};
class EditGroupDialog::Private
{
friend class ::Kleo::Dialogs::EditGroupDialog;
EditGroupDialog *const q;
struct {
QLineEdit *groupNameEdit = nullptr;
QLineEdit *availableKeysFilter = nullptr;
KeyTreeView *availableKeysList = nullptr;
QLineEdit *groupKeysFilter = nullptr;
KeyTreeView *groupKeysList = nullptr;
QDialogButtonBox *buttonBox = nullptr;
QComboBox *combo = nullptr;
} ui;
AbstractKeyListModel *availableKeysModel = nullptr;
AbstractKeyListModel *groupKeysModel = nullptr;
KeyGroup keyGroup;
std::vector<GpgME::Key> oldKeys;
FiltersProxyModel *filtersProxyModel = nullptr;
public:
Private(EditGroupDialog *qq)
: q(qq)
{
auto mainLayout = new QVBoxLayout(q);
{
auto groupNameLayout = new QHBoxLayout();
auto label = new QLabel(i18nc("Name of a group of keys", "Name:"), q);
groupNameLayout->addWidget(label);
ui.groupNameEdit = new QLineEdit(q);
label->setBuddy(ui.groupNameEdit);
groupNameLayout->addWidget(ui.groupNameEdit);
mainLayout->addLayout(groupNameLayout);
}
mainLayout->addWidget(new KSeparator(Qt::Horizontal, q));
auto centerLayout = new QVBoxLayout;
- auto availableKeysGroupBox = new QGroupBox{i18nc("@title", "Available Keys"), q};
+ auto availableKeysGroupBox = new QGroupBox{i18nc("@title", "Available Certificates"), q};
availableKeysGroupBox->setFlat(true);
auto availableKeysLayout = new QVBoxLayout{availableKeysGroupBox};
{
auto hbox = new QHBoxLayout;
auto label = new QLabel{i18nc("@label", "Search:")};
- label->setAccessibleName(i18nc("@label", "Search available keys"));
- label->setToolTip(i18nc("@info:tooltip", "Search the list of available keys for keys matching the search term."));
+ label->setAccessibleName(i18nc("@label", "Search available certificates"));
+ label->setToolTip(i18nc("@info:tooltip", "Search the list of available certificates for any that match the search term."));
hbox->addWidget(label);
ui.availableKeysFilter = new QLineEdit(q);
ui.availableKeysFilter->setClearButtonEnabled(true);
- ui.availableKeysFilter->setAccessibleName(i18nc("@label", "Search available keys"));
- ui.availableKeysFilter->setToolTip(i18nc("@info:tooltip", "Search the list of available keys for keys matching the search term."));
+ ui.availableKeysFilter->setAccessibleName(i18nc("@label", "Search available certificates"));
+ ui.availableKeysFilter->setToolTip(i18nc("@info:tooltip", "Search the list of available certificates for any that match the search term."));
ui.availableKeysFilter->setPlaceholderText(i18nc("@info::placeholder", "Enter search term"));
ui.availableKeysFilter->setCursorPosition(0); // prevent emission of accessible text cursor event before accessible focus event
label->setBuddy(ui.availableKeysFilter);
hbox->addWidget(ui.availableKeysFilter, 1);
ui.combo = new QComboBox;
ui.combo->setAccessibleName(i18n("Filter certificates by category"));
ui.combo->setToolTip(i18nc("@info:tooltip", "Show only certificates that belong to the selected category."));
hbox->addWidget(ui.combo);
filtersProxyModel = new FiltersProxyModel{q};
auto keyFilterModel = new KeyFilterModel{q};
std::shared_ptr<DefaultKeyFilter> filter;
filter = std::make_shared<DefaultKeyFilter>();
filter->setCanEncrypt(DefaultKeyFilter::Set);
filter->setIsBad(DefaultKeyFilter::NotSet);
filter->setName(i18n("Usable for Encryption"));
filter->setDescription(i18n("Certificates that can be used for encryption"));
filter->setId(QStringLiteral("CanEncryptFilter"));
filter->setMatchContexts(KeyFilter::Filtering);
keyFilterModel->prependCustomFilter(filter);
filtersProxyModel->setSourceModel(keyFilterModel);
filtersProxyModel->sort(0, Qt::AscendingOrder);
ui.combo->setModel(filtersProxyModel);
connect(ui.combo, &QComboBox::currentIndexChanged, q, [this](int index) {
const auto filter =
filtersProxyModel->data(filtersProxyModel->index(index, 0), KeyFilterManager::FilterRole).value<std::shared_ptr<KeyFilter>>();
ui.availableKeysList->setKeyFilter(filter);
});
availableKeysLayout->addLayout(hbox);
}
availableKeysModel = AbstractKeyListModel::createFlatKeyListModel(q);
availableKeysModel->setKeys(KeyCache::instance()->keys());
auto proxyModel = new WarnNonEncryptionKeysProxyModel(WarnNonEncryptionKeysProxyModel::Disable, q);
proxyModel->setSourceModel(availableKeysModel);
ui.availableKeysList = new KeyTreeView({}, nullptr, proxyModel, q, {});
- ui.availableKeysList->view()->setAccessibleName(i18n("available keys"));
+ ui.availableKeysList->view()->setAccessibleName(i18nc("@label", "Available certificates"));
ui.availableKeysList->view()->setRootIsDecorated(false);
ui.availableKeysList->setFlatModel(availableKeysModel);
ui.availableKeysList->setHierarchicalView(false);
if (!Settings{}.cmsEnabled()) {
ui.availableKeysList->setKeyFilter(createOpenPGPOnlyKeyFilter());
}
availableKeysLayout->addWidget(ui.availableKeysList, /*stretch=*/1);
centerLayout->addWidget(availableKeysGroupBox, /*stretch=*/1);
auto buttonsLayout = new QHBoxLayout;
buttonsLayout->addStretch(1);
auto addButton = new QPushButton(q);
addButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
- addButton->setAccessibleName(i18nc("@action:button", "Add Selected Keys"));
- addButton->setToolTip(i18nc("@info:tooltip", "Add the selected keys to the group"));
+ addButton->setAccessibleName(i18nc("@action:button", "Add Selected Certificates"));
+ addButton->setToolTip(i18nc("@info:tooltip", "Add the selected certificates to the group"));
addButton->setEnabled(false);
buttonsLayout->addWidget(addButton);
auto removeButton = new QPushButton(q);
removeButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
- removeButton->setAccessibleName(i18nc("@action:button", "Remove Selected Keys"));
- removeButton->setToolTip(i18nc("@info:tooltip", "Remove the selected keys from the group"));
+ removeButton->setAccessibleName(i18nc("@action:button", "Remove Selected Certificates"));
+ removeButton->setToolTip(i18nc("@info:tooltip", "Remove the selected certificates from the group"));
removeButton->setEnabled(false);
buttonsLayout->addWidget(removeButton);
buttonsLayout->addStretch(1);
centerLayout->addLayout(buttonsLayout);
- auto groupKeysGroupBox = new QGroupBox{i18nc("@title", "Group Keys"), q};
+ auto groupKeysGroupBox = new QGroupBox{i18nc("@title", "Certificates in the Group"), q};
groupKeysGroupBox->setFlat(true);
auto groupKeysLayout = new QVBoxLayout{groupKeysGroupBox};
{
auto hbox = new QHBoxLayout;
auto label = new QLabel{i18nc("@label", "Search:")};
- label->setAccessibleName(i18nc("@label", "Search group keys"));
- label->setToolTip(i18nc("@info:tooltip", "Search the list of group keys for keys matching the search term."));
+ label->setAccessibleName(i18nc("@label", "Search for certificates in the group"));
+ label->setToolTip(i18nc("@info:tooltip", "Search the list of certificates in the group for any that match the search term."));
hbox->addWidget(label);
ui.groupKeysFilter = new QLineEdit(q);
ui.groupKeysFilter->setClearButtonEnabled(true);
- ui.groupKeysFilter->setAccessibleName(i18nc("@label", "Search group keys"));
- ui.groupKeysFilter->setToolTip(i18nc("@info:tooltip", "Search the list of group keys for keys matching the search term."));
+ ui.groupKeysFilter->setAccessibleName(i18nc("@label", "Search for certificates in the group"));
+ ui.groupKeysFilter->setToolTip(i18nc("@info:tooltip", "Search the list of certificates in the group for any that match the search term."));
ui.groupKeysFilter->setPlaceholderText(i18nc("@info::placeholder", "Enter search term"));
ui.groupKeysFilter->setCursorPosition(0); // prevent emission of accessible text cursor event before accessible focus event
label->setBuddy(ui.groupKeysFilter);
hbox->addWidget(ui.groupKeysFilter, 1);
groupKeysLayout->addLayout(hbox);
}
groupKeysModel = AbstractKeyListModel::createFlatKeyListModel(q);
auto warnNonEncryptionProxyModel = new WarnNonEncryptionKeysProxyModel(WarnNonEncryptionKeysProxyModel::Warn, q);
ui.groupKeysList = new KeyTreeView({}, nullptr, warnNonEncryptionProxyModel, q, {});
- ui.groupKeysList->view()->setAccessibleName(i18n("group keys"));
+ ui.groupKeysList->view()->setAccessibleName(i18nc("@label", "Certificates in group"));
ui.groupKeysList->view()->setRootIsDecorated(false);
ui.groupKeysList->setFlatModel(groupKeysModel);
ui.groupKeysList->setHierarchicalView(false);
groupKeysLayout->addWidget(ui.groupKeysList, /*stretch=*/1);
centerLayout->addWidget(groupKeysGroupBox, /*stretch=*/1);
mainLayout->addLayout(centerLayout);
mainLayout->addWidget(new KSeparator(Qt::Horizontal, q));
ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, q);
QPushButton *saveButton = ui.buttonBox->button(QDialogButtonBox::Save);
KGuiItem::assign(saveButton, KStandardGuiItem::save());
KGuiItem::assign(ui.buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
saveButton->setEnabled(false);
mainLayout->addWidget(ui.buttonBox);
// prevent accidental closing of dialog when pressing Enter while a search field has focus
Kleo::unsetAutoDefaultButtons(q);
connect(ui.groupNameEdit, &QLineEdit::textChanged, q, [saveButton](const QString &text) {
saveButton->setEnabled(!text.trimmed().isEmpty());
});
connect(ui.availableKeysFilter, &QLineEdit::textChanged, ui.availableKeysList, &KeyTreeView::setStringFilter);
connect(ui.availableKeysList->view()->selectionModel(),
&QItemSelectionModel::selectionChanged,
q,
[addButton, this](const QItemSelection &, const QItemSelection &) {
addButton->setEnabled(ui.availableKeysList->selectedKeys().size() > 0);
});
connect(ui.availableKeysList->view(), &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) {
showKeyDetails(index);
});
connect(ui.groupKeysFilter, &QLineEdit::textChanged, ui.groupKeysList, &KeyTreeView::setStringFilter);
connect(ui.groupKeysList->view()->selectionModel(),
&QItemSelectionModel::selectionChanged,
q,
[removeButton](const QItemSelection &selected, const QItemSelection &) {
removeButton->setEnabled(!selected.isEmpty());
});
connect(ui.groupKeysList->view(), &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) {
showKeyDetails(index);
});
connect(addButton, &QPushButton::clicked, q, [this]() {
addKeysToGroup();
});
connect(removeButton, &QPushButton::clicked, q, [this]() {
removeKeysFromGroup();
});
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditGroupDialog::accept);
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditGroupDialog::reject);
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, q, [this] {
updateFromKeyCache();
});
// calculate default size with enough space for the key list
const auto fm = q->fontMetrics();
const QSize sizeHint = q->sizeHint();
const QSize defaultSize = QSize(qMax(sizeHint.width(), 150 * fm.horizontalAdvance(QLatin1Char('x'))), sizeHint.height());
restoreLayout(defaultSize);
for (auto i = 0; i < filtersProxyModel->rowCount(); ++i) {
if (filtersProxyModel->index(i, 0).data(KeyFilterManager::FilterIdRole).toString() == QLatin1StringView("CanEncryptFilter")) {
ui.combo->setCurrentIndex(i);
break;
};
}
}
~Private()
{
saveLayout();
}
private:
void saveLayout()
{
KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("EditGroupDialog"));
configGroup.writeEntry("Size", q->size());
configGroup.sync();
}
void restoreLayout(const QSize &defaultSize)
{
const KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("EditGroupDialog"));
const KConfigGroup availableKeysConfig = configGroup.group(QStringLiteral("AvailableKeysView"));
ui.availableKeysList->restoreLayout(availableKeysConfig);
const KConfigGroup groupKeysConfig = configGroup.group(QStringLiteral("GroupKeysView"));
ui.groupKeysList->restoreLayout(groupKeysConfig);
const QSize size = configGroup.readEntry("Size", defaultSize);
if (size.isValid()) {
q->resize(size);
}
}
void showKeyDetails(const QModelIndex &index)
{
if (!index.isValid()) {
return;
}
const auto key = index.model()->data(index, KeyList::KeyRole).value<GpgME::Key>();
if (!key.isNull()) {
auto cmd = new DetailsCommand(key);
cmd->setParentWidget(q);
cmd->start();
}
}
void addKeysToGroup();
void removeKeysFromGroup();
void updateFromKeyCache();
};
void EditGroupDialog::Private::addKeysToGroup()
{
const std::vector<Key> selectedGroupKeys = ui.groupKeysList->selectedKeys();
std::vector<Key> selectedKeys = ui.availableKeysList->selectedKeys();
// NOTE: This seems to be only necessary on Qt5. I've added it here for ease
// of backporting. We can remove it after backporting.
Kleo::erase_if(selectedKeys, [](const auto &key) {
return !Kleo::canBeUsedForEncryption(key);
});
groupKeysModel->addKeys(selectedKeys);
for (const Key &key : selectedKeys) {
availableKeysModel->removeKey(key);
}
ui.groupKeysList->selectKeys(selectedGroupKeys);
}
void EditGroupDialog::Private::removeKeysFromGroup()
{
const auto selectedOtherKeys = ui.availableKeysList->selectedKeys();
const std::vector<Key> selectedKeys = ui.groupKeysList->selectedKeys();
for (const Key &key : selectedKeys) {
groupKeysModel->removeKey(key);
}
availableKeysModel->addKeys(selectedKeys);
ui.availableKeysList->selectKeys(selectedOtherKeys);
}
void EditGroupDialog::Private::updateFromKeyCache()
{
const auto selectedGroupKeys = ui.groupKeysList->selectedKeys();
const auto selectedOtherKeys = ui.availableKeysList->selectedKeys();
const auto wasGroupKey = [this](const Key &key) {
return std::ranges::any_of(oldKeys, [key](const auto &k) {
return _detail::ByFingerprint<std::equal_to>()(k, key);
});
};
const auto allKeys = KeyCache::instance()->keys();
std::vector<Key> groupKeys;
groupKeys.reserve(allKeys.size());
std::vector<Key> otherKeys;
otherKeys.reserve(otherKeys.size());
std::partition_copy(allKeys.begin(), allKeys.end(), std::back_inserter(groupKeys), std::back_inserter(otherKeys), wasGroupKey);
groupKeysModel->setKeys(groupKeys);
availableKeysModel->setKeys(otherKeys);
ui.groupKeysList->selectKeys(selectedGroupKeys);
ui.availableKeysList->selectKeys(selectedOtherKeys);
}
EditGroupDialog::EditGroupDialog(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
setWindowTitle(i18nc("@title:window", "Edit Group"));
}
EditGroupDialog::~EditGroupDialog() = default;
void EditGroupDialog::setInitialFocus(FocusWidget widget)
{
switch (widget) {
case GroupName:
d->ui.groupNameEdit->setFocus();
break;
case KeysFilter:
d->ui.availableKeysFilter->setFocus();
break;
default:
qCDebug(KLEOPATRA_LOG) << "EditGroupDialog::setInitialFocus - invalid focus widget:" << widget;
}
}
void EditGroupDialog::showEvent(QShowEvent *event)
{
QDialog::showEvent(event);
// prevent accidental closing of dialog when pressing Enter while a search field has focus
Kleo::unsetDefaultButtons(d->ui.buttonBox);
}
KeyGroup EditGroupDialog::keyGroup() const
{
std::vector<Key> keys;
keys.reserve(d->groupKeysModel->rowCount());
for (int row = 0; row < d->groupKeysModel->rowCount(); ++row) {
const QModelIndex index = d->groupKeysModel->index(row, 0);
keys.push_back(d->groupKeysModel->key(index));
}
d->keyGroup.setKeys(keys);
d->keyGroup.setName(d->ui.groupNameEdit->text().trimmed());
return d->keyGroup;
}
void EditGroupDialog::setKeyGroup(const KeyGroup &keyGroup)
{
d->keyGroup = keyGroup;
const auto &keys = keyGroup.keys();
d->oldKeys = std::vector<GpgME::Key>(keys.begin(), keys.end());
d->groupKeysModel->setKeys(d->oldKeys);
// update the keys in the "available keys" list
const auto isGroupKey = [keys](const Key &key) {
return std::ranges::any_of(keys, [key](const auto &k) {
return _detail::ByFingerprint<std::equal_to>()(k, key);
});
};
auto otherKeys = KeyCache::instance()->keys();
Kleo::erase_if(otherKeys, isGroupKey);
d->availableKeysModel->setKeys(otherKeys);
d->ui.groupNameEdit->setText(keyGroup.name());
}
#include "editgroupdialog.moc"
#include "moc_editgroupdialog.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 23, 1:20 PM (8 m, 59 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7f/ae/d0e3618a509a7941e71d007293ea
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment