Messagelib

configurethemesdialog.cpp
1 /******************************************************************************
2  *
3  * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <[email protected]>
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 
32 namespace MessageList
33 {
34 namespace Utils
35 {
36 class ThemeListWidgetItem : public QListWidgetItem
37 {
38 public:
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  Q_REQUIRED_RESULT Core::Theme *theme() const
51  {
52  return mTheme;
53  }
54 
55  void forgetTheme()
56  {
57  mTheme = nullptr;
58  }
59 
60 private:
61  Core::Theme *mTheme = nullptr;
62 };
63 
64 class ThemeListWidget : public QListWidget
65 {
66 public:
67  ThemeListWidget(QWidget *parent)
68  : QListWidget(parent)
69  {
70  }
71 
72 public:
73  // need a larger but shorter QListWidget
74  Q_REQUIRED_RESULT QSize sizeHint() const override
75  {
76  return {450, 128};
77  }
78 };
79 } // namespace Utils
80 } // namespace MessageList
81 
82 using namespace MessageList::Core;
83 using namespace MessageList::Utils;
84 
85 ConfigureThemesDialog::ConfigureThemesDialog(QWidget *parent)
86  : QDialog(parent)
87  , d(new ConfigureThemesDialogPrivate(this))
88 {
90  auto mainLayout = new QVBoxLayout(this);
92  QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
93  okButton->setDefault(true);
94  okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
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 
181 ConfigureThemesDialog::~ConfigureThemesDialog() = default;
182 
183 void 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 
192 void 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 
214 void 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 
237 void 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 
254 void 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 
264 void 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 
281 ThemeListWidgetItem *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 
297 ThemeListWidgetItem *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 
315 ThemeListWidgetItem *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 
331 QString 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 
349 void 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 
378 void 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 
402 void 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 
434 void 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 
464 void 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"
QMap::const_iterator constBegin() const const
AlignVCenter
QWidget(QWidget *parent, Qt::WindowFlags f)
QString number(int n, int base)
void setShortcut(const QKeySequence &key)
void setName(const QString &name)
Sets the name of this OptionSet.
Definition: optionset.h:69
int count(const T &value) const const
void clicked(bool checked)
QIcon fromTheme(const QString &name)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
const QString & name() const
Returns the name of this OptionSet.
Definition: optionset.h:59
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void generateUniqueId()
(Re)generates a (hopefully) unique identifier for this option set.
Definition: optionset.cpp:40
KGuiItem cancel()
QString i18n(const char *text, const TYPE &arg...)
QMap::const_iterator constEnd() const const
void detach()
Detaches this object from the shared runtime data for columns.
Definition: theme.cpp:946
bool isEmpty() const const
void setWindowTitle(const QString &)
bool isEmpty() const const
Key_Return
KSharedConfigPtr config()
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
void addColumn(Column *column)
Appends a column to this theme.
Definition: theme.cpp:985
KGuiItem del()
The Theme class defines the visual appearance of the MessageList.
Definition: theme.h:48
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString name(StandardShortcut id)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void resetColumnState()
Resets the column state (visibility and width) to their default values (the "visible by default" ones...
Definition: theme.cpp:953
void setDefault(bool)
WA_DeleteOnClose
const QList< QKeySequence > & end()
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
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Mar 24 2023 04:08:30 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.