Messagelib

configureaggregationsdialog.cpp
1/******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9#include "utils/configureaggregationsdialog.h"
10#include "utils/configureaggregationsdialog_p.h"
11
12#include "core/aggregation.h"
13#include "utils/aggregationeditor.h"
14
15#include "core/manager.h"
16
17#include <QFrame>
18#include <QGridLayout>
19#include <QMap>
20#include <QPushButton>
21
22#include <KConfig>
23#include <KConfigGroup>
24#include <KLocalizedString>
25#include <QDialogButtonBox>
26#include <QFileDialog>
27#include <QIcon>
28#include <QVBoxLayout>
29
30namespace MessageList
31{
32namespace Utils
33{
34class AggregationListWidgetItem : public QListWidgetItem
35{
36private:
37 Core::Aggregation *mAggregation = nullptr;
38
39public:
40 AggregationListWidgetItem(QListWidget *par, const Core::Aggregation &set)
41 : QListWidgetItem(set.name(), par)
42 {
43 mAggregation = new Core::Aggregation(set);
44 }
45
46 ~AggregationListWidgetItem() override
47 {
48 delete mAggregation;
49 }
50
51public:
52 [[nodiscard]] Core::Aggregation *aggregation() const
53 {
54 return mAggregation;
55 }
56
57 void forgetAggregation()
58 {
59 mAggregation = nullptr;
60 }
61};
62
63/**
64 * The widget that lists the available Aggregations.
65 *
66 * At the moment of writing, derived from QListWidget only to override sizeHint().
67 */
68class AggregationListWidget : public QListWidget
69{
70public:
71 AggregationListWidget(QWidget *parent)
73 {
74 }
75
76public:
77 // need a larger but shorter QListWidget
78 [[nodiscard]] QSize sizeHint() const override
79 {
80 return {450, 128};
81 }
82};
83} // namespace Utils
84} // namespace MessageList
85
86using namespace MessageList::Core;
87using namespace MessageList::Utils;
88
89ConfigureAggregationsDialog::ConfigureAggregationsDialog(QWidget *parent)
90 : QDialog(parent)
91 , d(new ConfigureAggregationsDialogPrivate(this))
92{
93 setAttribute(Qt::WA_DeleteOnClose);
95 auto mainLayout = new QVBoxLayout(this);
96 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
97 okButton->setDefault(true);
99 connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigureAggregationsDialog::reject);
100 setWindowTitle(i18nc("@title:window", "Customize Message Aggregation Modes"));
101
102 auto base = new QWidget(this);
103 mainLayout->addWidget(base);
104 mainLayout->addWidget(buttonBox);
105
106 auto g = new QGridLayout(base);
107 g->setContentsMargins({});
108
109 d->mAggregationList = new AggregationListWidget(base);
110 d->mAggregationList->setSelectionMode(QAbstractItemView::ExtendedSelection);
111 d->mAggregationList->setSortingEnabled(true);
112 g->addWidget(d->mAggregationList, 0, 0, 7, 1);
113
114 connect(d->mAggregationList, &AggregationListWidget::itemClicked, this, [this](QListWidgetItem *item) {
115 d->aggregationListItemClicked(item);
116 });
117 connect(d->mAggregationList, &AggregationListWidget::currentItemChanged, this, [this](QListWidgetItem *item) {
118 d->aggregationListItemClicked(item);
119 });
120
121 d->mNewAggregationButton = new QPushButton(i18n("New Aggregation"), base);
122 d->mNewAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
123 g->addWidget(d->mNewAggregationButton, 0, 1);
124
125 connect(d->mNewAggregationButton, &QPushButton::clicked, this, [this]() {
126 d->newAggregationButtonClicked();
127 });
128
129 d->mCloneAggregationButton = new QPushButton(i18n("Clone Aggregation"), base);
130 d->mCloneAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
131 g->addWidget(d->mCloneAggregationButton, 1, 1);
132
133 connect(d->mCloneAggregationButton, &QPushButton::clicked, this, [this]() {
134 d->cloneAggregationButtonClicked();
135 });
136
137 auto f = new QFrame(base);
138 f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
139 f->setMinimumHeight(24);
140 g->addWidget(f, 2, 1, Qt::AlignVCenter);
141
142 d->mExportAggregationButton = new QPushButton(i18n("Export Aggregation..."), base);
143 g->addWidget(d->mExportAggregationButton, 3, 1);
144
145 connect(d->mExportAggregationButton, &QPushButton::clicked, this, [this]() {
146 d->exportAggregationButtonClicked();
147 });
148
149 d->mImportAggregationButton = new QPushButton(i18n("Import Aggregation..."), base);
150 g->addWidget(d->mImportAggregationButton, 4, 1);
151 connect(d->mImportAggregationButton, &QPushButton::clicked, this, [this]() {
152 d->importAggregationButtonClicked();
153 });
154
155 f = new QFrame(base);
156 f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
157 f->setMinimumHeight(24);
158 g->addWidget(f, 5, 1, Qt::AlignVCenter);
159
160 d->mDeleteAggregationButton = new QPushButton(i18n("Delete Aggregation"), base);
161 d->mDeleteAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
162 g->addWidget(d->mDeleteAggregationButton, 6, 1);
163
164 connect(d->mDeleteAggregationButton, &QPushButton::clicked, this, [this]() {
165 d->deleteAggregationButtonClicked();
166 });
167
168 d->mEditor = new AggregationEditor(base);
169 g->addWidget(d->mEditor, 8, 0, 1, 2);
170
171 connect(d->mEditor, &AggregationEditor::aggregationNameChanged, this, [this]() {
172 d->editedAggregationNameChanged();
173 });
174
175 g->setColumnStretch(0, 1);
176 g->setRowStretch(7, 1);
177
178 connect(okButton, &QPushButton::clicked, this, [this]() {
179 d->okButtonClicked();
180 });
181
182 d->fillAggregationList();
183}
184
185ConfigureAggregationsDialog::~ConfigureAggregationsDialog() = default;
186
187void ConfigureAggregationsDialog::selectAggregation(const QString &aggregationId)
188{
189 AggregationListWidgetItem *item = d->findAggregationItemById(aggregationId);
190 if (item) {
191 d->mAggregationList->setCurrentItem(item);
192 d->aggregationListItemClicked(item);
193 }
194}
195
196void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::okButtonClicked()
197{
198 if (Manager::instance()) {
199 commitEditor();
200
201 Manager::instance()->removeAllAggregations();
202
203 const int c = mAggregationList->count();
204 int i = 0;
205 while (i < c) {
206 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i));
207 if (item) {
208 Manager::instance()->addAggregation(item->aggregation());
209 item->forgetAggregation();
210 }
211 ++i;
212 }
213
214 Manager::instance()->aggregationsConfigurationCompleted();
215 }
216 Q_EMIT q->okClicked();
217 q->accept(); // this will delete too
218}
219
220void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::commitEditor()
221{
222 Aggregation *editedAggregation = mEditor->editedAggregation();
223 if (!editedAggregation) {
224 return;
225 }
226
227 mEditor->commit();
228
229 AggregationListWidgetItem *editedItem = findAggregationItemByAggregation(editedAggregation);
230 if (!editedItem) {
231 return;
232 }
233 const QString goodName = uniqueNameForAggregation(editedAggregation->name(), editedAggregation);
234 editedAggregation->setName(goodName);
235 editedItem->setText(goodName);
236}
237
238void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::editedAggregationNameChanged()
239{
240 Aggregation *set = mEditor->editedAggregation();
241 if (!set) {
242 return;
243 }
244
245 AggregationListWidgetItem *it = findAggregationItemByAggregation(set);
246 if (!it) {
247 return;
248 }
249
250 const QString goodName = uniqueNameForAggregation(set->name(), set);
251
252 it->setText(goodName);
253}
254
255void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::fillAggregationList()
256{
257 if (!Manager::instance()) {
258 return;
259 }
260 const QMap<QString, Aggregation *> &sets = Manager::instance()->aggregations();
262 for (QMap<QString, Aggregation *>::ConstIterator it = sets.constBegin(); it != end; ++it) {
263 (void)new AggregationListWidgetItem(mAggregationList, *(*it));
264 }
265}
266
267void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::aggregationListItemClicked(QListWidgetItem *cur)
268{
269 commitEditor();
270 updateButton(cur);
271}
272
273void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::updateButton(QListWidgetItem *cur)
274{
275 const int numberOfSelectedItem(mAggregationList->selectedItems().count());
276
277 AggregationListWidgetItem *item = cur ? dynamic_cast<AggregationListWidgetItem *>(cur) : nullptr;
278 mDeleteAggregationButton->setEnabled(item && !item->aggregation()->readOnly() && (mAggregationList->count() > 1));
279
280 mCloneAggregationButton->setEnabled(numberOfSelectedItem == 1);
281 mExportAggregationButton->setEnabled(numberOfSelectedItem > 0);
282 mEditor->editAggregation(item ? item->aggregation() : nullptr);
283 if (item && !item->isSelected()) {
284 item->setSelected(true); // make sure it's true
285 }
286}
287
288AggregationListWidgetItem *ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::findAggregationItemByName(const QString &name,
289 Aggregation *skipAggregation)
290{
291 const int c = mAggregationList->count();
292 int i = 0;
293 while (i < c) {
294 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i));
295 if (item) {
296 if (item->aggregation() != skipAggregation) {
297 if (item->aggregation()->name() == name) {
298 return item;
299 }
300 }
301 }
302 ++i;
303 }
304 return nullptr;
305}
306
307AggregationListWidgetItem *ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::findAggregationItemById(const QString &aggregationId)
308{
309 const int c = mAggregationList->count();
310 int i = 0;
311 while (i < c) {
312 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i));
313 if (item) {
314 if (item->aggregation()->id() == aggregationId) {
315 return item;
316 }
317 }
318 ++i;
319 }
320 return nullptr;
321}
322
323AggregationListWidgetItem *ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::findAggregationItemByAggregation(Aggregation *set)
324{
325 const int c = mAggregationList->count();
326 int i = 0;
327 while (i < c) {
328 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->item(i));
329 if (item) {
330 if (item->aggregation() == set) {
331 return item;
332 }
333 }
334 ++i;
335 }
336 return nullptr;
337}
338
339QString ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::uniqueNameForAggregation(const QString &baseName, Aggregation *skipAggregation)
340{
341 QString ret = baseName;
342 if (ret.isEmpty()) {
343 ret = i18n("Unnamed Aggregation");
344 }
345
346 int idx = 1;
347
348 AggregationListWidgetItem *item = findAggregationItemByName(ret, skipAggregation);
349 while (item) {
350 idx++;
351 ret = QStringLiteral("%1 %2").arg(baseName).arg(idx);
352 item = findAggregationItemByName(ret, skipAggregation);
353 }
354 return ret;
355}
356
357void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::newAggregationButtonClicked()
358{
359 Aggregation emptyAggregation;
360 emptyAggregation.setName(uniqueNameForAggregation(i18n("New Aggregation")));
361 auto item = new AggregationListWidgetItem(mAggregationList, emptyAggregation);
362
363 mAggregationList->clearSelection();
364 mAggregationList->setCurrentItem(item);
365 mDeleteAggregationButton->setEnabled(item && !item->aggregation()->readOnly());
366}
367
368void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::cloneAggregationButtonClicked()
369{
370 auto item = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->currentItem());
371 if (!item) {
372 return;
373 }
374 commitEditor();
375 item->setSelected(false);
376 Aggregation copyAggregation(*(item->aggregation()));
377 copyAggregation.setReadOnly(false);
378 copyAggregation.generateUniqueId(); // regenerate id so it becomes different
379 copyAggregation.setName(uniqueNameForAggregation(item->aggregation()->name()));
380 item = new AggregationListWidgetItem(mAggregationList, copyAggregation);
381
382 mAggregationList->setCurrentItem(item);
383 aggregationListItemClicked(item);
384}
385
386void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::deleteAggregationButtonClicked()
387{
388 const QList<QListWidgetItem *> list = mAggregationList->selectedItems();
389 if (list.isEmpty()) {
390 return;
391 }
392
393 mEditor->editAggregation(nullptr); // forget it
394 for (QListWidgetItem *it : list) {
395 auto item = dynamic_cast<AggregationListWidgetItem *>(it);
396 if (!item) {
397 return;
398 }
399 if (!item->aggregation()->readOnly()) {
400 delete item; // this will trigger aggregationListCurrentItemChanged()
401 }
402 if (mAggregationList->count() < 2) {
403 break; // no way: desperately try to keep at least one option set alive :)
404 }
405 }
406
407 auto newItem = dynamic_cast<AggregationListWidgetItem *>(mAggregationList->currentItem());
408 updateButton(newItem);
409}
410
411void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::importAggregationButtonClicked()
412{
413 const QString filename = QFileDialog::getOpenFileName(q, i18n("Import Aggregation"));
414 if (!filename.isEmpty()) {
415 KConfig config(filename);
416
417 if (config.hasGroup(QStringLiteral("MessageListView::Aggregations"))) {
418 KConfigGroup grp(&config, QStringLiteral("MessageListView::Aggregations"));
419 const int cnt = grp.readEntry("Count", 0);
420 int idx = 0;
421 while (idx < cnt) {
422 const QString data = grp.readEntry(QStringLiteral("Set%1").arg(idx), QString());
423 if (!data.isEmpty()) {
424 auto set = new Aggregation();
425 if (set->loadFromString(data)) {
426 set->setReadOnly(false);
427 set->generateUniqueId(); // regenerate id so it becomes different
428 set->setName(uniqueNameForAggregation(set->name()));
429 (void)new AggregationListWidgetItem(mAggregationList, *set);
430 } else {
431 delete set; // b0rken
432 }
433 }
434 ++idx;
435 }
436 }
437 }
438}
439
440void ConfigureAggregationsDialog::ConfigureAggregationsDialogPrivate::exportAggregationButtonClicked()
441{
442 const QList<QListWidgetItem *> list = mAggregationList->selectedItems();
443 if (list.isEmpty()) {
444 return;
445 }
446 const QString filename = QFileDialog::getSaveFileName(q, i18n("Export Aggregation"), QString(), i18n("All Files (*)"));
447 if (!filename.isEmpty()) {
448 KConfig config(filename);
449
450 KConfigGroup grp(&config, QStringLiteral("MessageListView::Aggregations"));
451 grp.writeEntry("Count", list.count());
452
453 int idx = 0;
454 for (QListWidgetItem *item : list) {
455 auto themeItem = static_cast<AggregationListWidgetItem *>(item);
456 grp.writeEntry(QStringLiteral("Set%1").arg(idx), themeItem->aggregation()->saveToString());
457 ++idx;
458 }
459 }
460}
461
462#include "moc_configureaggregationsdialog.cpp"
A set of aggregation options that can be applied to the MessageList::Model in a single shot.
Definition aggregation.h:29
const QString & id() const
Returns the unique id of this OptionSet.
Definition optionset.h:51
void generateUniqueId()
(Re)generates a (hopefully) unique identifier for this option set.
Definition optionset.cpp:40
void setName(const QString &name)
Sets the name of this OptionSet.
Definition optionset.h:69
const QString & name() const
Returns the name of this OptionSet.
Definition optionset.h:59
bool loadFromString(const QString &data)
Attempts to unpack this configuration object from a string (that is likely to come out from a config ...
Definition optionset.cpp:69
A widget that allows editing a single MessageList::Aggregation.
void aggregationNameChanged()
This is triggered when the aggregation name changes in the editor text field.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
void clicked(bool checked)
void setShortcut(const QKeySequence &key)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QIcon fromTheme(const QString &name)
qsizetype count() const const
bool isEmpty() const const
bool isSelected() const const
void setSelected(bool select)
void setText(const QString &text)
const_iterator constBegin() const const
const_iterator constEnd() const const
QObject * parent() const const
T qobject_cast(QObject *object)
void setDefault(bool)
QString arg(Args &&... args) const const
bool isEmpty() const const
AlignVCenter
Key_Return
WA_DeleteOnClose
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.