diff --git a/src/conf/CMakeLists.txt b/src/conf/CMakeLists.txt index 618d09fc6..fdde1771a 100644 --- a/src/conf/CMakeLists.txt +++ b/src/conf/CMakeLists.txt @@ -1,88 +1,91 @@ include_directories(${kleopatra_SOURCE_DIR}/src) if(BUILD_libkleopatraclient) set(_kcm_kleopatra_libkleopatraclient_extra_SRCS smimevalidationconfigurationwidget.cpp smimevalidationconfigurationpage.cpp cryptooperationsconfigwidget.cpp cryptooperationsconfigpage.cpp ) ki18n_wrap_ui(_kcm_kleopatra_libkleopatraclient_extra_SRCS smimevalidationconfigurationwidget.ui ) kconfig_add_kcfg_files(_kcm_kleopatra_libkleopatraclient_extra_SRCS ${kleopatra_SOURCE_DIR}/src/kcfg/smimevalidationpreferences.kcfgc ) set(_kcm_kleopatra_libkleopatraclient_extra_LIBS kleopatraclientgui) set(_kcm_kleopatra_libkleopatraclient_extra_install_FILES kleopatra_config_smimevalidation.desktop kleopatra_config_cryptooperations.desktop ) else() set(_kcm_kleopatra_libkleopatraclient_extra_SRCS) set(_kcm_kleopatra_libkleopatraclient_extra_LIBS) set(_kcm_kleopatra_libkleopatraclient_extra_install_FILES) endif() set(kcm_kleopatra_PART_SRCS dirservconfigpage.cpp appearanceconfigpage.cpp appearanceconfigwidget.cpp gnupgsystemconfigurationpage.cpp + groupsconfigpage.cpp + groupsconfigwidget.cpp + ${kleopatra_SOURCE_DIR}/src/dialogs/editgroupdialog.cpp ${kleopatra_BINARY_DIR}/src/kleopatra_debug.cpp ${_kcm_kleopatra_libkleopatraclient_extra_SRCS} ) ki18n_wrap_ui(kcm_kleopatra_PART_SRCS appearanceconfigwidget.ui smimevalidationconfigurationwidget.ui ) kconfig_add_kcfg_files(kcm_kleopatra_PART_SRCS ${kleopatra_SOURCE_DIR}/src/kcfg/tooltippreferences.kcfgc ${kleopatra_SOURCE_DIR}/src/kcfg/emailoperationspreferences.kcfgc ${kleopatra_SOURCE_DIR}/src/kcfg/fileoperationspreferences.kcfgc ${kleopatra_SOURCE_DIR}/src/kcfg/tagspreferences.kcfgc ${kleopatra_SOURCE_DIR}/src/kcfg/settings.kcfgc ) add_library(kcm_kleopatra MODULE ${kcm_kleopatra_PART_SRCS}) if(HAVE_KCMUTILS) set (_kcm_kleopatra_extra_libs KF5::KCMUtils) endif() target_link_libraries(kcm_kleopatra KF5::Libkleo KF5::IconThemes KF5::I18n KF5::WidgetsAddons KF5::ConfigWidgets ${_kcm_kleopatra_extra_libs} ${_kleopatra_dbusaddons_libs} ${_kcm_kleopatra_libkleopatraclient_extra_LIBS} ) if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(kcm_kleopatra PROPERTIES UNITY_BUILD ON) endif() install(TARGETS kcm_kleopatra DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### install files ############### install(FILES kleopatra_config_dirserv.desktop kleopatra_config_appear.desktop kleopatra_config_gnupgsystem.desktop + kleopatra_config_groups.desktop ${_kcm_kleopatra_libkleopatraclient_extra_install_FILES} DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) - diff --git a/src/conf/configuredialog.cpp b/src/conf/configuredialog.cpp index 63b5bd7aa..086d0643f 100644 --- a/src/conf/configuredialog.cpp +++ b/src/conf/configuredialog.cpp @@ -1,70 +1,71 @@ /* configuredialog.cpp This file is part of kleopatra SPDX-FileCopyrightText: 2000 Espen Sand SPDX-FileCopyrightText: 2001-2002 Marc Mutz SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-only */ #include "configuredialog.h" #include #include #include #include #if HAVE_KCMUTILS # include #else # include "kleopageconfigdialog.h" #endif ConfigureDialog::ConfigureDialog(QWidget *parent) #if HAVE_KCMUTILS : KCMultiDialog(parent) #else : KleoPageConfigDialog(parent) #endif { setFaceType(KPageDialog::List); setWindowTitle(i18nc("@title:window", "Configure")); + addModule(QStringLiteral("kleopatra_config_groups")); addModule(QStringLiteral("kleopatra_config_dirserv")); addModule(QStringLiteral("kleopatra_config_appear")); addModule(QStringLiteral("kleopatra_config_cryptooperations")); addModule(QStringLiteral("kleopatra_config_smimevalidation")); addModule(QStringLiteral("kleopatra_config_gnupgsystem")); // We store the minimum size of the dialog on hide, because otherwise // the KCMultiDialog starts with the size of the first kcm, not // the largest one. This way at least after the first showing of // the largest kcm the size is kept. const KConfigGroup geometry(KSharedConfig::openConfig(), "Geometry"); const int width = geometry.readEntry("ConfigureDialogWidth", 0); const int height = geometry.readEntry("ConfigureDialogHeight", 0); if (width != 0 && height != 0) { setMinimumSize(width, height); } } void ConfigureDialog::hideEvent(QHideEvent *e) { const QSize minSize = minimumSizeHint(); KConfigGroup geometry(KSharedConfig::openConfig(), "Geometry"); geometry.writeEntry("ConfigureDialogWidth", minSize.width()); geometry.writeEntry("ConfigureDialogHeight", minSize.height()); #if HAVE_KCMUTILS KCMultiDialog::hideEvent(e); #else KleoPageConfigDialog::hideEvent(e); #endif } ConfigureDialog::~ConfigureDialog() { } diff --git a/src/conf/groupsconfigpage.cpp b/src/conf/groupsconfigpage.cpp new file mode 100644 index 000000000..e89179855 --- /dev/null +++ b/src/conf/groupsconfigpage.cpp @@ -0,0 +1,89 @@ +/* + conf/groupsconfigpage.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "groupsconfigpage.h" + +#include "groupsconfigwidget.h" + +#include +#include +#include + +#include + +#include "kleopatra_debug.h" + +using namespace Kleo; + +class GroupsConfigPage::Private +{ + friend class ::GroupsConfigPage; + GroupsConfigPage *const q; + + Private(GroupsConfigPage *qq); +public: + ~Private() = default; + +private: + GroupsConfigWidget *widget = nullptr; +}; + +GroupsConfigPage::Private::Private(GroupsConfigPage *qq) + : q(qq) +{ +} + +GroupsConfigPage::GroupsConfigPage(QWidget *parent, const QVariantList &args) + : KCModule(parent, args) + , d(new Private(this)) +{ + // do not show the Defaults button; it has no effect + setButtons(buttons() & ~KCModule::Default); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + d->widget = new Kleo::GroupsConfigWidget(this); + if (QLayout *l = d->widget->layout()) { + l->setContentsMargins(0, 0, 0, 0); + } + + layout->addWidget(d->widget); + + connect(d->widget, &GroupsConfigWidget::changed, this, &GroupsConfigPage::markAsChanged); +} + +GroupsConfigPage::~GroupsConfigPage() = default; + +void GroupsConfigPage::load() +{ + qCDebug(KLEOPATRA_LOG) << "GroupsConfigPage::load()"; + d->widget->setGroups(KeyCache::instance()->configurableGroups()); +} + +void GroupsConfigPage::save() +{ + KeyCache::mutableInstance()->saveConfigurableGroups(d->widget->groups()); + + // reload after saving to ensure that the groups reflect the saved groups (e.g. in case of immutable entries) + load(); +} + +extern "C" +{ + Q_DECL_EXPORT KCModule *create_kleopatra_config_groups(QWidget *parent = nullptr, const QVariantList &args = QVariantList()) + { + GroupsConfigPage *page = new GroupsConfigPage(parent, args); + page->setObjectName(QStringLiteral("kleopatra_config_groups")); + return page; + } +} diff --git a/src/conf/groupsconfigpage.h b/src/conf/groupsconfigpage.h new file mode 100644 index 000000000..b4b3432a4 --- /dev/null +++ b/src/conf/groupsconfigpage.h @@ -0,0 +1,31 @@ +/* + conf/groupsconfigpage.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __KLEOPATRA_CONF_GROUPSCONFIGPAGE_H__ +#define __KLEOPATRA_CONF_GROUPSCONFIGPAGE_H__ + +#include + +class GroupsConfigPage : public KCModule +{ + Q_OBJECT +public: + explicit GroupsConfigPage(QWidget *parent = nullptr, const QVariantList &args = QVariantList()); + ~GroupsConfigPage() override; + + void load() override; + void save() override; + +private: + class Private; + const std::unique_ptr d; +}; + +#endif // __KLEOPATRA_CONF_GROUPSCONFIGPAGE_H__ diff --git a/src/conf/groupsconfigwidget.cpp b/src/conf/groupsconfigwidget.cpp new file mode 100644 index 000000000..eaf98df3b --- /dev/null +++ b/src/conf/groupsconfigwidget.cpp @@ -0,0 +1,278 @@ +/* + conf/groupsconfigwidget.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "groupsconfigwidget.h" + +#include "dialogs/editgroupdialog.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kleopatra_debug.h" + +using namespace Kleo; +using namespace Kleo::Dialogs; + +Q_DECLARE_METATYPE(KeyGroup) + +namespace +{ + +class ProxyModel : public AbstractKeyListSortFilterProxyModel +{ + Q_OBJECT +public: + ProxyModel(QObject *parent = nullptr) + : AbstractKeyListSortFilterProxyModel(parent) + { + } + + ~ProxyModel() override = default; + + ProxyModel *clone() const override + { + // compiler-generated copy ctor is fine! + return new ProxyModel(*this); + } +}; + +} + +class GroupsConfigWidget::Private +{ + friend class ::Kleo::GroupsConfigWidget; + GroupsConfigWidget *const q; + + struct { + QLineEdit *groupsFilter = nullptr; + QListView *groupsList = nullptr; + QPushButton *newButton = nullptr; + QPushButton *editButton = nullptr; + QPushButton *deleteButton = nullptr; + } ui; + AbstractKeyListModel *groupsModel = nullptr; + ProxyModel *groupsFilterModel = nullptr; + +public: + Private(GroupsConfigWidget *qq) + : q(qq) + { + auto mainLayout = new QVBoxLayout(q); + + auto groupsLayout = new QGridLayout(); + groupsLayout->setColumnStretch(0, 1); + groupsLayout->setRowStretch(1, 1); + + ui.groupsFilter = new QLineEdit(); + ui.groupsFilter->setClearButtonEnabled(true); + ui.groupsFilter->setPlaceholderText(i18nc("Placeholder text", "Search...")); + groupsLayout->addWidget(ui.groupsFilter, 0, 0); + + groupsModel = AbstractKeyListModel::createFlatKeyListModel(q); + groupsFilterModel = new ProxyModel(q); + groupsFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + groupsFilterModel->setFilterKeyColumn(KeyList::Summary); + groupsFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + groupsFilterModel->setSourceModel(groupsModel); + groupsFilterModel->sort(KeyList::Summary, Qt::AscendingOrder); + ui.groupsList = new QListView(); + ui.groupsList->setModel(groupsFilterModel); + ui.groupsList->setModelColumn(KeyList::Summary); + ui.groupsList->setSelectionBehavior(QAbstractItemView::SelectRows); + ui.groupsList->setSelectionMode(QAbstractItemView::SingleSelection); + + groupsLayout->addWidget(ui.groupsList, 1, 0); + + auto groupsButtonLayout = new QVBoxLayout(); + + ui.newButton = new QPushButton(i18n("New")); + groupsButtonLayout->addWidget(ui.newButton); + + ui.editButton = new QPushButton(i18n("Edit")); + ui.editButton->setEnabled(false); + groupsButtonLayout->addWidget(ui.editButton); + + ui.deleteButton = new QPushButton(i18n("Delete")); + ui.deleteButton->setEnabled(false); + groupsButtonLayout->addWidget(ui.deleteButton); + + groupsButtonLayout->addStretch(1); + + groupsLayout->addLayout(groupsButtonLayout, 1, 1); + + mainLayout->addLayout(groupsLayout, /*stretch=*/ 1); + + connect(ui.groupsFilter, &QLineEdit::textChanged, groupsFilterModel, &QSortFilterProxyModel::setFilterFixedString); + connect(ui.groupsList->selectionModel(), &QItemSelectionModel::selectionChanged, + q, [this] () { selectionChanged(); }); + connect(ui.groupsList, &QListView::doubleClicked, + q, [this] (const QModelIndex &index) { editGroup(index); }); + connect(ui.newButton, &QPushButton::clicked, q, [this] () { addGroup(); }); + connect(ui.editButton, &QPushButton::clicked, q, [this] () { editGroup(); }); + connect(ui.deleteButton, &QPushButton::clicked, q, [this] () { deleteGroup(); }); + } + + ~Private() + { + } + +private: + QModelIndex selectedIndex() + { + const QModelIndexList selected = ui.groupsList->selectionModel()->selectedRows(); + return selected.empty() ? QModelIndex() : selected[0]; + } + + KeyGroup getGroup(const QModelIndex &index) + { + return index.isValid() ? ui.groupsList->model()->data(index, KeyList::GroupRole).value() : KeyGroup(); + } + + void selectionChanged() + { + const KeyGroup selectedGroup = getGroup(selectedIndex()); + const bool selectedGroupIsEditable = !selectedGroup.isNull() && !selectedGroup.isImmutable(); + ui.editButton->setEnabled(selectedGroupIsEditable); + ui.deleteButton->setEnabled(selectedGroupIsEditable); + } + + KeyGroup showEditGroupDialog(KeyGroup group, const QString &windowTitle, EditGroupDialog::FocusWidget focusWidget) + { + auto dialog = std::make_unique(q); + dialog->setWindowTitle(windowTitle); + dialog->setGroupName(group.name()); + const KeyGroup::Keys &keys = group.keys(); + dialog->setGroupKeys(std::vector(keys.cbegin(), keys.cend())); + dialog->setInitialFocus(focusWidget); + + const int result = dialog->exec(); + if (result == QDialog::Rejected) { + return KeyGroup(); + } + + group.setName(dialog->groupName()); + group.setKeys(dialog->groupKeys()); + + return group; + } + + void addGroup() + { + const KeyGroup::Id newId = KRandom::randomString(8); + KeyGroup group = KeyGroup(newId, i18nc("default name for new group of keys", "New Group"), {}, KeyGroup::ApplicationConfig); + group.setIsImmutable(false); + + const KeyGroup newGroup = showEditGroupDialog( + group, i18nc("@title:window a group of keys", "New Group"), EditGroupDialog::GroupName); + if (newGroup.isNull()) { + return; + } + + const QModelIndex newIndex = groupsModel->addGroup(newGroup); + if (!newIndex.isValid()) { + qCDebug(KLEOPATRA_LOG) << "Adding group to model failed"; + return; + } + + Q_EMIT q->changed(); + } + + void editGroup(const QModelIndex &index = QModelIndex()) + { + const QModelIndex groupIndex = index.isValid() ? index : selectedIndex(); + if (!groupIndex.isValid()) { + qCDebug(KLEOPATRA_LOG) << "selection is empty"; + return; + } + const KeyGroup group = getGroup(groupIndex); + if (group.isNull()) { + qCDebug(KLEOPATRA_LOG) << "selected group is null"; + return; + } + if (group.isImmutable()) { + qCDebug(KLEOPATRA_LOG) << "selected group is immutable"; + return; + } + + const KeyGroup updatedGroup = showEditGroupDialog( + group, i18nc("@title:window a group of keys", "Edit Group"), EditGroupDialog::KeysFilter); + if (updatedGroup.isNull()) { + return; + } + + const bool success = ui.groupsList->model()->setData(groupIndex, QVariant::fromValue(updatedGroup)); + if (!success) { + qCDebug(KLEOPATRA_LOG) << "Updating group in model failed"; + return; + } + + Q_EMIT q->changed(); + } + + void deleteGroup() + { + const QModelIndex groupIndex = selectedIndex(); + if (!groupIndex.isValid()) { + qCDebug(KLEOPATRA_LOG) << "selection is empty"; + return; + } + const KeyGroup group = getGroup(groupIndex); + if (group.isNull()) { + qCDebug(KLEOPATRA_LOG) << "selected group is null"; + return; + } + + const bool success = groupsModel->removeGroup(group); + if (!success) { + qCDebug(KLEOPATRA_LOG) << "Removing group from model failed"; + return; + } + + Q_EMIT q->changed(); + } +}; + +GroupsConfigWidget::GroupsConfigWidget(QWidget *parent) + : QWidget(parent) + , d(new Private(this)) +{ +} + +GroupsConfigWidget::~GroupsConfigWidget() = default; + +void GroupsConfigWidget::setGroups(const std::vector &groups) +{ + d->groupsModel->setGroups(groups); +} + +std::vector GroupsConfigWidget::groups() const +{ + std::vector result; + result.reserve(d->groupsModel->rowCount()); + for (int row = 0; row < d->groupsModel->rowCount(); ++row) { + const QModelIndex index = d->groupsModel->index(row, 0); + result.push_back(d->groupsModel->group(index)); + } + return result; +} + +#include "groupsconfigwidget.moc" diff --git a/src/conf/groupsconfigwidget.h b/src/conf/groupsconfigwidget.h new file mode 100644 index 000000000..ceb66a050 --- /dev/null +++ b/src/conf/groupsconfigwidget.h @@ -0,0 +1,42 @@ +/* + conf/groupsconfigwidget.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef __KLEOPATRA_CONF_GROUPSCONFIGWIDGET_H__ +#define __KLEOPATRA_CONF_GROUPSCONFIGWIDGET_H__ + +#include + +#include + +namespace Kleo +{ +class KeyGroup; + +class GroupsConfigWidget : public QWidget +{ + Q_OBJECT +public: + explicit GroupsConfigWidget(QWidget *parent = nullptr); + ~GroupsConfigWidget() override; + + void setGroups(const std::vector &groups); + std::vector groups() const; + +Q_SIGNALS: + void changed(); + +private: + class Private; + const std::unique_ptr d; +}; + +} + +#endif // __KLEOPATRA_CONF_GROUPSCONFIGWIDGET_H__ diff --git a/src/conf/kleopatra_config_groups.desktop b/src/conf/kleopatra_config_groups.desktop new file mode 100644 index 000000000..44bf87dc2 --- /dev/null +++ b/src/conf/kleopatra_config_groups.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Icon=group +Type=Service +X-KDE-ServiceTypes=KCModule +X-DocPath=kleopatra/configuration.html + +X-KDE-ModuleType=Library +X-KDE-Library=kcm_kleopatra +X-KDE-FactoryName=kleopatra_config_groups +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=kleopatra +X-KDE-ParentComponents=kleopatra +X-KDE-CfgDlgHierarchy=Kleopatra + +Name=Groups +Comment=Configuration of groups of keys +X-KDE-Keywords=groups,keys,certificates,configuration