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

KDE's Doxygen guidelines are available online.