Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34388862
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
18 KB
Subscribers
None
View Options
diff --git a/src/dialogs/editgroupdialog.cpp b/src/dialogs/editgroupdialog.cpp
index fedb337a7..155de657b 100644
--- a/src/dialogs/editgroupdialog.cpp
+++ b/src/dialogs/editgroupdialog.cpp
@@ -1,487 +1,490 @@
/*
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/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <KColorScheme>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QApplication>
#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;
}
}
class WarnNonEncryptionKeysProxyModel : public Kleo::AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
using Kleo::AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel;
~WarnNonEncryptionKeysProxyModel() override;
WarnNonEncryptionKeysProxyModel *clone() const override
{
return new WarnNonEncryptionKeysProxyModel(this->parent());
}
QVariant data(const QModelIndex &index, int role) const override
{
const auto sourceIndex = sourceModel()->index(index.row(), index.column());
if (!Kleo::keyHasEncrypt(sourceIndex.data(KeyList::KeyRole).value<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.");
}
}
return sourceIndex.data(role);
}
};
WarnNonEncryptionKeysProxyModel::~WarnNonEncryptionKeysProxyModel() = default;
class DisableNonEncryptionKeysProxyModel : public Kleo::AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
using Kleo::AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel;
~DisableNonEncryptionKeysProxyModel() override;
DisableNonEncryptionKeysProxyModel *clone() const override
{
return new DisableNonEncryptionKeysProxyModel(this->parent());
}
QVariant data(const QModelIndex &index, int role) const override
{
const auto sourceIndex = sourceModel()->index(index.row(), index.column());
if (!Kleo::keyHasEncrypt(sourceIndex.data(KeyList::KeyRole).value<Key>())) {
if (role == Qt::ForegroundRole) {
return qApp->palette().color(QPalette::Disabled, QPalette::Text);
}
if (role == Qt::BackgroundRole) {
return KColorScheme(QPalette::Disabled, KColorScheme::View).background(KColorScheme::NeutralBackground).color();
}
if (role == Qt::ToolTipRole) {
return i18nc("@info:tooltip", "This certificate cannot be added to the group as it cannot be used for encryption.");
}
}
return sourceIndex.data(role);
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
auto originalFlags = index.model()->QAbstractItemModel::flags(index);
if (Kleo::keyHasEncrypt(index.data(KeyList::KeyRole).value<Key>())) {
return originalFlags;
} else {
return (originalFlags & ~Qt::ItemIsEnabled);
}
return {};
}
};
DisableNonEncryptionKeysProxyModel::~DisableNonEncryptionKeysProxyModel() = default;
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;
} ui;
AbstractKeyListModel *availableKeysModel = nullptr;
AbstractKeyListModel *groupKeysModel = nullptr;
KeyGroup keyGroup;
std::vector<GpgME::Key> oldKeys;
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};
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."));
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->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);
availableKeysLayout->addLayout(hbox);
}
availableKeysModel = AbstractKeyListModel::createFlatKeyListModel(q);
availableKeysModel->setKeys(KeyCache::instance()->keys());
auto proxyModel = new DisableNonEncryptionKeysProxyModel(q);
proxyModel->setSourceModel(availableKeysModel);
ui.availableKeysList = new KeyTreeView({}, nullptr, proxyModel, q, {});
ui.availableKeysList->view()->setAccessibleName(i18n("available keys"));
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->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->setEnabled(false);
buttonsLayout->addWidget(removeButton);
buttonsLayout->addStretch(1);
centerLayout->addLayout(buttonsLayout);
auto groupKeysGroupBox = new QGroupBox{i18nc("@title", "Group Keys"), 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."));
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->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(q);
ui.groupKeysList = new KeyTreeView({}, nullptr, warnNonEncryptionProxyModel, q, {});
ui.groupKeysList->view()->setAccessibleName(i18n("group keys"));
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](const QItemSelection &selected, const QItemSelection &) {
addButton->setEnabled(!selected.isEmpty());
});
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);
}
~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();
const std::vector<Key> selectedKeys = ui.availableKeysList->selectedKeys();
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
Sun, Jan 4, 2:27 PM (1 d, 18 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b4/32/e51463e673d7bd397617fbcb8a0b
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment