Messagelib

configurethemesdialog.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/configurethemesdialog.h"
10#include "utils/configurethemesdialog_p.h"
11
12#include "core/theme.h"
13#include "utils/themeeditor.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 <KMessageBox>
26#include <QDialogButtonBox>
27#include <QFileDialog>
28#include <QIcon>
29#include <QListWidget>
30#include <QVBoxLayout>
31
32namespace MessageList
33{
34namespace Utils
35{
36class ThemeListWidgetItem : public QListWidgetItem
37{
38public:
39 ThemeListWidgetItem(QListWidget *par, const Core::Theme &set)
40 : QListWidgetItem(set.name(), par)
41 {
42 mTheme = new Core::Theme(set);
43 }
44
45 ~ThemeListWidgetItem() override
46 {
47 delete mTheme;
48 }
49
50 [[nodiscard]] Core::Theme *theme() const
51 {
52 return mTheme;
53 }
54
55 void forgetTheme()
56 {
57 mTheme = nullptr;
58 }
59
60private:
61 Core::Theme *mTheme = nullptr;
62};
63
64class ThemeListWidget : public QListWidget
65{
66public:
67 ThemeListWidget(QWidget *parent)
69 {
70 }
71
72public:
73 // need a larger but shorter QListWidget
74 [[nodiscard]] QSize sizeHint() const override
75 {
76 return {450, 128};
77 }
78};
79} // namespace Utils
80} // namespace MessageList
81
82using namespace MessageList::Core;
83using namespace MessageList::Utils;
84
85ConfigureThemesDialog::ConfigureThemesDialog(QWidget *parent)
86 : QDialog(parent)
87 , d(new ConfigureThemesDialogPrivate(this))
88{
89 setAttribute(Qt::WA_DeleteOnClose);
90 auto mainLayout = new QVBoxLayout(this);
92 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
93 okButton->setDefault(true);
95 connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigureThemesDialog::reject);
96 setWindowTitle(i18nc("@title:window", "Customize Themes"));
97
98 auto base = new QWidget(this);
99 mainLayout->addWidget(base);
100 mainLayout->addWidget(buttonBox);
101
102 auto g = new QGridLayout(base);
103 g->setContentsMargins({});
104
105 d->mThemeList = new ThemeListWidget(base);
106 d->mThemeList->setSelectionMode(QAbstractItemView::ExtendedSelection);
107 d->mThemeList->setSortingEnabled(true);
108 g->addWidget(d->mThemeList, 0, 0, 7, 1);
109
110 connect(d->mThemeList, &ThemeListWidget::currentItemChanged, this, [this](QListWidgetItem *item) {
111 d->themeListItemClicked(item);
112 });
113 connect(d->mThemeList, &ThemeListWidget::itemClicked, this, [this](QListWidgetItem *item) {
114 d->themeListItemClicked(item);
115 });
116
117 d->mNewThemeButton = new QPushButton(i18n("New Theme"), base);
118 d->mNewThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
119 g->addWidget(d->mNewThemeButton, 0, 1);
120
121 connect(d->mNewThemeButton, &QPushButton::clicked, this, [this]() {
122 d->newThemeButtonClicked();
123 });
124
125 d->mCloneThemeButton = new QPushButton(i18n("Clone Theme"), base);
126 d->mCloneThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
127 g->addWidget(d->mCloneThemeButton, 1, 1);
128
129 connect(d->mCloneThemeButton, &QPushButton::clicked, this, [this]() {
130 d->cloneThemeButtonClicked();
131 });
132
133 auto f = new QFrame(base);
134 f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
135 f->setMinimumHeight(24);
136 g->addWidget(f, 2, 1, Qt::AlignVCenter);
137
138 d->mExportThemeButton = new QPushButton(i18n("Export Theme..."), base);
139 g->addWidget(d->mExportThemeButton, 3, 1);
140
141 connect(d->mExportThemeButton, &QPushButton::clicked, this, [this]() {
142 d->exportThemeButtonClicked();
143 });
144
145 d->mImportThemeButton = new QPushButton(i18n("Import Theme..."), base);
146 g->addWidget(d->mImportThemeButton, 4, 1);
147 connect(d->mImportThemeButton, &QPushButton::clicked, this, [this]() {
148 d->importThemeButtonClicked();
149 });
150
151 f = new QFrame(base);
152 f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
153 f->setMinimumHeight(24);
154 g->addWidget(f, 5, 1, Qt::AlignVCenter);
155
156 d->mDeleteThemeButton = new QPushButton(i18n("Delete Theme"), base);
157 d->mDeleteThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
158 g->addWidget(d->mDeleteThemeButton, 6, 1);
159
160 connect(d->mDeleteThemeButton, &QPushButton::clicked, this, [this]() {
161 d->deleteThemeButtonClicked();
162 });
163
164 d->mEditor = new ThemeEditor(base);
165 g->addWidget(d->mEditor, 8, 0, 1, 2);
166
167 connect(d->mEditor, &ThemeEditor::themeNameChanged, this, [this]() {
168 d->editedThemeNameChanged();
169 });
170
171 g->setColumnStretch(0, 1);
172 g->setRowStretch(4, 1);
173
174 connect(okButton, &QPushButton::clicked, this, [this]() {
175 d->okButtonClicked();
176 });
177
178 d->fillThemeList();
179}
180
181ConfigureThemesDialog::~ConfigureThemesDialog() = default;
182
183void ConfigureThemesDialog::selectTheme(const QString &themeId)
184{
185 ThemeListWidgetItem *item = d->findThemeItemById(themeId);
186 if (item) {
187 d->mThemeList->setCurrentItem(item);
188 d->themeListItemClicked(item);
189 }
190}
191
192void ConfigureThemesDialog::ConfigureThemesDialogPrivate::okButtonClicked()
193{
194 commitEditor();
195
196 Manager::instance()->removeAllThemes();
197
198 const int c = mThemeList->count();
199 int i = 0;
200 while (i < c) {
201 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i));
202 if (item) {
203 Manager::instance()->addTheme(item->theme());
204 item->forgetTheme();
205 }
206 ++i;
207 }
208
209 Manager::instance()->themesConfigurationCompleted();
210 Q_EMIT q->okClicked();
211 q->accept(); // this will delete too
212}
213
214void ConfigureThemesDialog::ConfigureThemesDialogPrivate::commitEditor()
215{
216 Theme *editedTheme = mEditor->editedTheme();
217 if (!editedTheme) {
218 return;
219 }
220
221 mEditor->commit();
222
223 ThemeListWidgetItem *editedItem = findThemeItemByTheme(editedTheme);
224 if (!editedItem) {
225 return;
226 }
227
228 // We must reset the runtime column state as the columns might have
229 // totally changed in the editor
230 editedTheme->resetColumnState();
231
232 QString goodName = uniqueNameForTheme(editedTheme->name(), editedTheme);
233 editedTheme->setName(goodName);
234 editedItem->setText(goodName);
235}
236
237void ConfigureThemesDialog::ConfigureThemesDialogPrivate::editedThemeNameChanged()
238{
239 Theme *set = mEditor->editedTheme();
240 if (!set) {
241 return;
242 }
243
244 ThemeListWidgetItem *it = findThemeItemByTheme(set);
245 if (!it) {
246 return;
247 }
248
249 QString goodName = uniqueNameForTheme(set->name(), set);
250
251 it->setText(goodName);
252}
253
254void ConfigureThemesDialog::ConfigureThemesDialogPrivate::fillThemeList()
255{
256 const QMap<QString, Theme *> &sets = Manager::instance()->themes();
257
259 for (QMap<QString, Theme *>::ConstIterator it = sets.constBegin(); it != end; ++it) {
260 (void)new ThemeListWidgetItem(mThemeList, *(*it));
261 }
262}
263
264void ConfigureThemesDialog::ConfigureThemesDialogPrivate::themeListItemClicked(QListWidgetItem *cur)
265{
266 commitEditor();
267
268 const int numberOfSelectedItem(mThemeList->selectedItems().count());
269
270 ThemeListWidgetItem *item = cur ? dynamic_cast<ThemeListWidgetItem *>(cur) : nullptr;
271 mDeleteThemeButton->setEnabled(item && !item->theme()->readOnly());
272 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
273 mEditor->editTheme(item ? item->theme() : nullptr);
274 mExportThemeButton->setEnabled(numberOfSelectedItem > 0);
275
276 if (item && !item->isSelected()) {
277 item->setSelected(true); // make sure it's true
278 }
279}
280
281ThemeListWidgetItem *ConfigureThemesDialog::ConfigureThemesDialogPrivate::findThemeItemById(const QString &themeId)
282{
283 const int c = mThemeList->count();
284 int i = 0;
285 while (i < c) {
286 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i));
287 if (item) {
288 if (item->theme()->id() == themeId) {
289 return item;
290 }
291 }
292 ++i;
293 }
294 return nullptr;
295}
296
297ThemeListWidgetItem *ConfigureThemesDialog::ConfigureThemesDialogPrivate::findThemeItemByName(const QString &name, Theme *skipTheme)
298{
299 const int c = mThemeList->count();
300 int i = 0;
301 while (i < c) {
302 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i));
303 if (item) {
304 if (item->theme() != skipTheme) {
305 if (item->theme()->name() == name) {
306 return item;
307 }
308 }
309 }
310 ++i;
311 }
312 return nullptr;
313}
314
315ThemeListWidgetItem *ConfigureThemesDialog::ConfigureThemesDialogPrivate::findThemeItemByTheme(Theme *set)
316{
317 const int c = mThemeList->count();
318 int i = 0;
319 while (i < c) {
320 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i));
321 if (item) {
322 if (item->theme() == set) {
323 return item;
324 }
325 }
326 ++i;
327 }
328 return nullptr;
329}
330
331QString ConfigureThemesDialog::ConfigureThemesDialogPrivate::uniqueNameForTheme(const QString &baseName, Theme *skipTheme)
332{
333 QString ret = baseName;
334 if (ret.isEmpty()) {
335 ret = i18n("Unnamed Theme");
336 }
337
338 int idx = 1;
339
340 ThemeListWidgetItem *item = findThemeItemByName(ret, skipTheme);
341 while (item) {
342 idx++;
343 ret = QStringLiteral("%1 %2").arg(baseName, QString::number(idx));
344 item = findThemeItemByName(ret, skipTheme);
345 }
346 return ret;
347}
348
349void ConfigureThemesDialog::ConfigureThemesDialogPrivate::newThemeButtonClicked()
350{
351 const int numberOfSelectedItem(mThemeList->selectedItems().count());
352 Theme emptyTheme;
353 emptyTheme.setName(uniqueNameForTheme(i18n("New Theme")));
354 auto col = new Theme::Column();
355 col->setLabel(i18n("New Column"));
356 col->setVisibleByDefault(true);
357 col->addMessageRow(new Theme::Row());
358 col->addGroupHeaderRow(new Theme::Row());
359 emptyTheme.addColumn(col);
360 auto item = new ThemeListWidgetItem(mThemeList, emptyTheme);
361
362 mThemeList->clearSelection();
363 mThemeList->setCurrentItem(item);
364 Core::Theme *theme = item->theme();
365 if (theme) {
366 mEditor->editTheme(theme);
367
368 mDeleteThemeButton->setEnabled(!theme->readOnly());
369 mExportThemeButton->setEnabled(item);
370 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
371 } else {
372 mDeleteThemeButton->setEnabled(false);
373 mExportThemeButton->setEnabled(false);
374 mCloneThemeButton->setEnabled(false);
375 }
376}
377
378void ConfigureThemesDialog::ConfigureThemesDialogPrivate::cloneThemeButtonClicked()
379{
380 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->currentItem());
381 if (!item) {
382 return;
383 }
384 commitEditor();
385 item->setSelected(false);
386 Theme copyTheme(*(item->theme()));
387 copyTheme.setReadOnly(false);
388 copyTheme.detach(); // detach shared data
389 copyTheme.generateUniqueId(); // regenerate id so it becomes different
390 copyTheme.setName(uniqueNameForTheme(item->theme()->name()));
391 item = new ThemeListWidgetItem(mThemeList, copyTheme);
392
393 mThemeList->setCurrentItem(item);
394 mEditor->editTheme(item->theme());
395
396 const int numberOfSelectedItem(mThemeList->selectedItems().count());
397 mDeleteThemeButton->setEnabled(!item->theme()->readOnly());
398 mExportThemeButton->setEnabled(true);
399 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
400}
401
402void ConfigureThemesDialog::ConfigureThemesDialogPrivate::deleteThemeButtonClicked()
403{
404 const QList<QListWidgetItem *> list = mThemeList->selectedItems();
405 if (list.isEmpty()) {
406 return;
407 }
408 const QString question = list.count() > 1 ? i18n("Do you want to delete selected themes?") : i18n("Do you want to delete \"%1\"?", list.first()->text());
409 const int answer =
410 KMessageBox::questionTwoActions(q, question, i18nc("@title:window", "Delete Theme"), KStandardGuiItem::del(), KStandardGuiItem::cancel());
411 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
412 mEditor->editTheme(nullptr); // forget it
413 for (QListWidgetItem *it : list) {
414 auto item = dynamic_cast<ThemeListWidgetItem *>(it);
415 if (!item) {
416 return;
417 }
418 if (!item->theme()->readOnly()) {
419 delete item; // this will trigger themeListCurrentItemChanged()
420 }
421 if (mThemeList->count() < 2) {
422 break; // no way: desperately try to keep at least one option set alive :)
423 }
424 }
425
426 auto newItem = dynamic_cast<ThemeListWidgetItem *>(mThemeList->currentItem());
427 mDeleteThemeButton->setEnabled(newItem && !newItem->theme()->readOnly());
428 mExportThemeButton->setEnabled(newItem);
429 const int numberOfSelectedItem(mThemeList->selectedItems().count());
430 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
431 }
432}
433
434void ConfigureThemesDialog::ConfigureThemesDialogPrivate::importThemeButtonClicked()
435{
436 const QString filename = QFileDialog::getOpenFileName(q, i18n("Import Theme"));
437 if (!filename.isEmpty()) {
438 KConfig config(filename);
439
440 if (config.hasGroup(QStringLiteral("MessageListView::Themes"))) {
441 KConfigGroup grp(&config, QStringLiteral("MessageListView::Themes"));
442 const int cnt = grp.readEntry("Count", 0);
443 int idx = 0;
444 while (idx < cnt) {
445 const QString data = grp.readEntry(QStringLiteral("Set%1").arg(idx), QString());
446 if (!data.isEmpty()) {
447 auto set = new Theme();
448 if (set->loadFromString(data)) {
449 set->setReadOnly(false);
450 set->detach(); // detach shared data
451 set->generateUniqueId(); // regenerate id so it becomes different
452 set->setName(uniqueNameForTheme(set->name()));
453 (void)new ThemeListWidgetItem(mThemeList, *set);
454 } else {
455 delete set;
456 }
457 }
458 ++idx;
459 }
460 }
461 }
462}
463
464void ConfigureThemesDialog::ConfigureThemesDialogPrivate::exportThemeButtonClicked()
465{
466 const QList<QListWidgetItem *> list = mThemeList->selectedItems();
467 if (list.isEmpty()) {
468 return;
469 }
470 const QString filename = QFileDialog::getSaveFileName(q, i18n("Export Theme"), QString(), i18n("All Files (*)"));
471 if (!filename.isEmpty()) {
472 KConfig config(filename);
473
474 KConfigGroup grp(&config, QStringLiteral("MessageListView::Themes"));
475 grp.writeEntry("Count", list.count());
476
477 int idx = 0;
478 for (QListWidgetItem *item : list) {
479 auto themeItem = static_cast<ThemeListWidgetItem *>(item);
480 grp.writeEntry(QStringLiteral("Set%1").arg(idx), themeItem->theme()->saveToString());
481 ++idx;
482 }
483 }
484}
485
486#include "moc_configurethemesdialog.cpp"
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
The Column class defines a view column available inside this theme.
Definition theme.h:506
The Row class defines a row of items inside a Column.
Definition theme.h:413
The Theme class defines the visual appearance of the MessageList.
Definition theme.h:48
void detach()
Detaches this object from the shared runtime data for columns.
Definition theme.cpp:946
void addColumn(Column *column)
Appends a column to this theme.
Definition theme.cpp:985
void resetColumnState()
Resets the column state (visibility and width) to their default values (the "visible by default" ones...
Definition theme.cpp:953
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem cancel()
KGuiItem del()
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
QString number(double n, char format, int precision)
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 Fri May 3 2024 11:43:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.