1 /*
2 conf/groupsconfigwidget.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 SPDX-FileCopyrightText: 2021 g10 Code GmbH
6 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "groupsconfigwidget.h"
12
13 #include "dialogs/editgroupdialog.h"
14
15 #include <Libkleo/KeyCache>
16 #include <Libkleo/KeyGroup>
17 #include <Libkleo/KeyListModel>
18 #include <Libkleo/KeyListSortFilterProxyModel>
19
20 #include <KLocalizedString>
21 #include <KRandom>
22
23 #include <QLabel>
24 #include <QLineEdit>
25 #include <QListView>
26 #include <QPointer>
27 #include <QPushButton>
28 #include <QVBoxLayout>
29
30 #include "kleopatra_debug.h"
31
32 using namespace Kleo;
33 using namespace Kleo::Dialogs;
34
35 Q_DECLARE_METATYPE(KeyGroup)
36
37 namespace
38 {
39
40 class ProxyModel : public AbstractKeyListSortFilterProxyModel
41 {
42 Q_OBJECT
43 public:
ProxyModel(QObject * parent=nullptr)44 ProxyModel(QObject *parent = nullptr)
45 : AbstractKeyListSortFilterProxyModel(parent)
46 {
47 }
48
49 ~ProxyModel() override = default;
50
clone() const51 ProxyModel *clone() const override
52 {
53 // compiler-generated copy ctor is fine!
54 return new ProxyModel(*this);
55 }
56 };
57
58 }
59
60 class GroupsConfigWidget::Private
61 {
62 friend class ::Kleo::GroupsConfigWidget;
63 GroupsConfigWidget *const q;
64
65 struct {
66 QLineEdit *groupsFilter = nullptr;
67 QListView *groupsList = nullptr;
68 QPushButton *newButton = nullptr;
69 QPushButton *editButton = nullptr;
70 QPushButton *deleteButton = nullptr;
71 } ui;
72 AbstractKeyListModel *groupsModel = nullptr;
73 ProxyModel *groupsFilterModel = nullptr;
74
75 public:
Private(GroupsConfigWidget * qq)76 Private(GroupsConfigWidget *qq)
77 : q(qq)
78 {
79 auto mainLayout = new QVBoxLayout(q);
80
81 auto groupsLayout = new QGridLayout();
82 groupsLayout->setColumnStretch(0, 1);
83 groupsLayout->setRowStretch(1, 1);
84
85 ui.groupsFilter = new QLineEdit();
86 ui.groupsFilter->setClearButtonEnabled(true);
87 ui.groupsFilter->setPlaceholderText(i18nc("Placeholder text", "Search..."));
88 groupsLayout->addWidget(ui.groupsFilter, 0, 0);
89
90 groupsModel = AbstractKeyListModel::createFlatKeyListModel(q);
91 groupsFilterModel = new ProxyModel(q);
92 groupsFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
93 groupsFilterModel->setFilterKeyColumn(KeyList::Summary);
94 groupsFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
95 groupsFilterModel->setSourceModel(groupsModel);
96 groupsFilterModel->sort(KeyList::Summary, Qt::AscendingOrder);
97 ui.groupsList = new QListView();
98 ui.groupsList->setModel(groupsFilterModel);
99 ui.groupsList->setModelColumn(KeyList::Summary);
100 ui.groupsList->setSelectionBehavior(QAbstractItemView::SelectRows);
101 ui.groupsList->setSelectionMode(QAbstractItemView::SingleSelection);
102
103 groupsLayout->addWidget(ui.groupsList, 1, 0);
104
105 auto groupsButtonLayout = new QVBoxLayout();
106
107 ui.newButton = new QPushButton(i18n("New"));
108 groupsButtonLayout->addWidget(ui.newButton);
109
110 ui.editButton = new QPushButton(i18n("Edit"));
111 ui.editButton->setEnabled(false);
112 groupsButtonLayout->addWidget(ui.editButton);
113
114 ui.deleteButton = new QPushButton(i18n("Delete"));
115 ui.deleteButton->setEnabled(false);
116 groupsButtonLayout->addWidget(ui.deleteButton);
117
118 groupsButtonLayout->addStretch(1);
119
120 groupsLayout->addLayout(groupsButtonLayout, 1, 1);
121
122 mainLayout->addLayout(groupsLayout, /*stretch=*/ 1);
123
124 connect(ui.groupsFilter, &QLineEdit::textChanged, groupsFilterModel, &QSortFilterProxyModel::setFilterFixedString);
125 connect(ui.groupsList->selectionModel(), &QItemSelectionModel::selectionChanged,
126 q, [this] () { selectionChanged(); });
127 connect(ui.groupsList, &QListView::doubleClicked,
128 q, [this] (const QModelIndex &index) { editGroup(index); });
129 connect(ui.newButton, &QPushButton::clicked, q, [this] () { addGroup(); });
130 connect(ui.editButton, &QPushButton::clicked, q, [this] () { editGroup(); });
131 connect(ui.deleteButton, &QPushButton::clicked, q, [this] () { deleteGroup(); });
132 }
133
~Private()134 ~Private()
135 {
136 }
137
138 private:
selectedIndex()139 QModelIndex selectedIndex()
140 {
141 const QModelIndexList selected = ui.groupsList->selectionModel()->selectedRows();
142 return selected.empty() ? QModelIndex() : selected[0];
143 }
144
getGroup(const QModelIndex & index)145 KeyGroup getGroup(const QModelIndex &index)
146 {
147 return index.isValid() ? ui.groupsList->model()->data(index, KeyList::GroupRole).value<KeyGroup>() : KeyGroup();
148 }
149
selectionChanged()150 void selectionChanged()
151 {
152 const KeyGroup selectedGroup = getGroup(selectedIndex());
153 const bool selectedGroupIsEditable = !selectedGroup.isNull() && !selectedGroup.isImmutable();
154 ui.editButton->setEnabled(selectedGroupIsEditable);
155 ui.deleteButton->setEnabled(selectedGroupIsEditable);
156 }
157
showEditGroupDialog(KeyGroup group,const QString & windowTitle,EditGroupDialog::FocusWidget focusWidget)158 KeyGroup showEditGroupDialog(KeyGroup group, const QString &windowTitle, EditGroupDialog::FocusWidget focusWidget)
159 {
160 auto dialog = std::make_unique<EditGroupDialog>(q);
161 dialog->setWindowTitle(windowTitle);
162 dialog->setGroupName(group.name());
163 const KeyGroup::Keys &keys = group.keys();
164 dialog->setGroupKeys(std::vector<GpgME::Key>(keys.cbegin(), keys.cend()));
165 dialog->setInitialFocus(focusWidget);
166
167 const int result = dialog->exec();
168 if (result == QDialog::Rejected) {
169 return KeyGroup();
170 }
171
172 group.setName(dialog->groupName());
173 group.setKeys(dialog->groupKeys());
174
175 return group;
176 }
177
addGroup()178 void addGroup()
179 {
180 const KeyGroup::Id newId = KRandom::randomString(8);
181 KeyGroup group = KeyGroup(newId, i18nc("default name for new group of keys", "New Group"), {}, KeyGroup::ApplicationConfig);
182 group.setIsImmutable(false);
183
184 const KeyGroup newGroup = showEditGroupDialog(
185 group, i18nc("@title:window a group of keys", "New Group"), EditGroupDialog::GroupName);
186 if (newGroup.isNull()) {
187 return;
188 }
189
190 const QModelIndex newIndex = groupsModel->addGroup(newGroup);
191 if (!newIndex.isValid()) {
192 qCDebug(KLEOPATRA_LOG) << "Adding group to model failed";
193 return;
194 }
195
196 Q_EMIT q->changed();
197 }
198
editGroup(const QModelIndex & index=QModelIndex ())199 void editGroup(const QModelIndex &index = QModelIndex())
200 {
201 const QModelIndex groupIndex = index.isValid() ? index : selectedIndex();
202 if (!groupIndex.isValid()) {
203 qCDebug(KLEOPATRA_LOG) << "selection is empty";
204 return;
205 }
206 const KeyGroup group = getGroup(groupIndex);
207 if (group.isNull()) {
208 qCDebug(KLEOPATRA_LOG) << "selected group is null";
209 return;
210 }
211 if (group.isImmutable()) {
212 qCDebug(KLEOPATRA_LOG) << "selected group is immutable";
213 return;
214 }
215
216 const KeyGroup updatedGroup = showEditGroupDialog(
217 group, i18nc("@title:window a group of keys", "Edit Group"), EditGroupDialog::KeysFilter);
218 if (updatedGroup.isNull()) {
219 return;
220 }
221
222 const bool success = ui.groupsList->model()->setData(groupIndex, QVariant::fromValue(updatedGroup));
223 if (!success) {
224 qCDebug(KLEOPATRA_LOG) << "Updating group in model failed";
225 return;
226 }
227
228 Q_EMIT q->changed();
229 }
230
deleteGroup()231 void deleteGroup()
232 {
233 const QModelIndex groupIndex = selectedIndex();
234 if (!groupIndex.isValid()) {
235 qCDebug(KLEOPATRA_LOG) << "selection is empty";
236 return;
237 }
238 const KeyGroup group = getGroup(groupIndex);
239 if (group.isNull()) {
240 qCDebug(KLEOPATRA_LOG) << "selected group is null";
241 return;
242 }
243
244 const bool success = groupsModel->removeGroup(group);
245 if (!success) {
246 qCDebug(KLEOPATRA_LOG) << "Removing group from model failed";
247 return;
248 }
249
250 Q_EMIT q->changed();
251 }
252 };
253
GroupsConfigWidget(QWidget * parent)254 GroupsConfigWidget::GroupsConfigWidget(QWidget *parent)
255 : QWidget(parent)
256 , d(new Private(this))
257 {
258 }
259
260 GroupsConfigWidget::~GroupsConfigWidget() = default;
261
setGroups(const std::vector<KeyGroup> & groups)262 void GroupsConfigWidget::setGroups(const std::vector<KeyGroup> &groups)
263 {
264 d->groupsModel->setGroups(groups);
265 }
266
groups() const267 std::vector<KeyGroup> GroupsConfigWidget::groups() const
268 {
269 std::vector<KeyGroup> result;
270 result.reserve(d->groupsModel->rowCount());
271 for (int row = 0; row < d->groupsModel->rowCount(); ++row) {
272 const QModelIndex index = d->groupsModel->index(row, 0);
273 result.push_back(d->groupsModel->group(index));
274 }
275 return result;
276 }
277
278 #include "groupsconfigwidget.moc"
279