File indexing completed on 2024-12-15 04:54:44
0001 /****************************************************************************** 0002 * 0003 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 * 0007 *******************************************************************************/ 0008 0009 #include "utils/configurethemesdialog.h" 0010 #include "utils/configurethemesdialog_p.h" 0011 0012 #include "core/theme.h" 0013 #include "utils/themeeditor.h" 0014 0015 #include "core/manager.h" 0016 0017 #include <QFrame> 0018 #include <QGridLayout> 0019 #include <QMap> 0020 #include <QPushButton> 0021 0022 #include <KConfig> 0023 #include <KConfigGroup> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <QDialogButtonBox> 0027 #include <QFileDialog> 0028 #include <QIcon> 0029 #include <QListWidget> 0030 #include <QVBoxLayout> 0031 0032 namespace MessageList 0033 { 0034 namespace Utils 0035 { 0036 class ThemeListWidgetItem : public QListWidgetItem 0037 { 0038 public: 0039 ThemeListWidgetItem(QListWidget *par, const Core::Theme &set) 0040 : QListWidgetItem(set.name(), par) 0041 { 0042 mTheme = new Core::Theme(set); 0043 } 0044 0045 ~ThemeListWidgetItem() override 0046 { 0047 delete mTheme; 0048 } 0049 0050 [[nodiscard]] Core::Theme *theme() const 0051 { 0052 return mTheme; 0053 } 0054 0055 void forgetTheme() 0056 { 0057 mTheme = nullptr; 0058 } 0059 0060 private: 0061 Core::Theme *mTheme = nullptr; 0062 }; 0063 0064 class ThemeListWidget : public QListWidget 0065 { 0066 public: 0067 ThemeListWidget(QWidget *parent) 0068 : QListWidget(parent) 0069 { 0070 } 0071 0072 public: 0073 // need a larger but shorter QListWidget 0074 [[nodiscard]] QSize sizeHint() const override 0075 { 0076 return {450, 128}; 0077 } 0078 }; 0079 } // namespace Utils 0080 } // namespace MessageList 0081 0082 using namespace MessageList::Core; 0083 using namespace MessageList::Utils; 0084 0085 ConfigureThemesDialog::ConfigureThemesDialog(QWidget *parent) 0086 : QDialog(parent) 0087 , d(new ConfigureThemesDialogPrivate(this)) 0088 { 0089 setAttribute(Qt::WA_DeleteOnClose); 0090 auto mainLayout = new QVBoxLayout(this); 0091 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0092 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0093 okButton->setDefault(true); 0094 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0095 connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigureThemesDialog::reject); 0096 setWindowTitle(i18nc("@title:window", "Customize Themes")); 0097 0098 auto base = new QWidget(this); 0099 mainLayout->addWidget(base); 0100 mainLayout->addWidget(buttonBox); 0101 0102 auto g = new QGridLayout(base); 0103 g->setContentsMargins({}); 0104 0105 d->mThemeList = new ThemeListWidget(base); 0106 d->mThemeList->setSelectionMode(QAbstractItemView::ExtendedSelection); 0107 d->mThemeList->setSortingEnabled(true); 0108 g->addWidget(d->mThemeList, 0, 0, 7, 1); 0109 0110 connect(d->mThemeList, &ThemeListWidget::currentItemChanged, this, [this](QListWidgetItem *item) { 0111 d->themeListItemClicked(item); 0112 }); 0113 connect(d->mThemeList, &ThemeListWidget::itemClicked, this, [this](QListWidgetItem *item) { 0114 d->themeListItemClicked(item); 0115 }); 0116 0117 d->mNewThemeButton = new QPushButton(i18n("New Theme"), base); 0118 d->mNewThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0119 g->addWidget(d->mNewThemeButton, 0, 1); 0120 0121 connect(d->mNewThemeButton, &QPushButton::clicked, this, [this]() { 0122 d->newThemeButtonClicked(); 0123 }); 0124 0125 d->mCloneThemeButton = new QPushButton(i18n("Clone Theme"), base); 0126 d->mCloneThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0127 g->addWidget(d->mCloneThemeButton, 1, 1); 0128 0129 connect(d->mCloneThemeButton, &QPushButton::clicked, this, [this]() { 0130 d->cloneThemeButtonClicked(); 0131 }); 0132 0133 auto f = new QFrame(base); 0134 f->setFrameStyle(QFrame::Sunken | QFrame::HLine); 0135 f->setMinimumHeight(24); 0136 g->addWidget(f, 2, 1, Qt::AlignVCenter); 0137 0138 d->mExportThemeButton = new QPushButton(i18n("Export Theme..."), base); 0139 g->addWidget(d->mExportThemeButton, 3, 1); 0140 0141 connect(d->mExportThemeButton, &QPushButton::clicked, this, [this]() { 0142 d->exportThemeButtonClicked(); 0143 }); 0144 0145 d->mImportThemeButton = new QPushButton(i18n("Import Theme..."), base); 0146 g->addWidget(d->mImportThemeButton, 4, 1); 0147 connect(d->mImportThemeButton, &QPushButton::clicked, this, [this]() { 0148 d->importThemeButtonClicked(); 0149 }); 0150 0151 f = new QFrame(base); 0152 f->setFrameStyle(QFrame::Sunken | QFrame::HLine); 0153 f->setMinimumHeight(24); 0154 g->addWidget(f, 5, 1, Qt::AlignVCenter); 0155 0156 d->mDeleteThemeButton = new QPushButton(i18n("Delete Theme"), base); 0157 d->mDeleteThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0158 g->addWidget(d->mDeleteThemeButton, 6, 1); 0159 0160 connect(d->mDeleteThemeButton, &QPushButton::clicked, this, [this]() { 0161 d->deleteThemeButtonClicked(); 0162 }); 0163 0164 d->mEditor = new ThemeEditor(base); 0165 g->addWidget(d->mEditor, 8, 0, 1, 2); 0166 0167 connect(d->mEditor, &ThemeEditor::themeNameChanged, this, [this]() { 0168 d->editedThemeNameChanged(); 0169 }); 0170 0171 g->setColumnStretch(0, 1); 0172 g->setRowStretch(4, 1); 0173 0174 connect(okButton, &QPushButton::clicked, this, [this]() { 0175 d->okButtonClicked(); 0176 }); 0177 0178 d->fillThemeList(); 0179 } 0180 0181 ConfigureThemesDialog::~ConfigureThemesDialog() = default; 0182 0183 void ConfigureThemesDialog::selectTheme(const QString &themeId) 0184 { 0185 ThemeListWidgetItem *item = d->findThemeItemById(themeId); 0186 if (item) { 0187 d->mThemeList->setCurrentItem(item); 0188 d->themeListItemClicked(item); 0189 } 0190 } 0191 0192 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::okButtonClicked() 0193 { 0194 commitEditor(); 0195 0196 Manager::instance()->removeAllThemes(); 0197 0198 const int c = mThemeList->count(); 0199 int i = 0; 0200 while (i < c) { 0201 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i)); 0202 if (item) { 0203 Manager::instance()->addTheme(item->theme()); 0204 item->forgetTheme(); 0205 } 0206 ++i; 0207 } 0208 0209 Manager::instance()->themesConfigurationCompleted(); 0210 Q_EMIT q->okClicked(); 0211 q->accept(); // this will delete too 0212 } 0213 0214 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::commitEditor() 0215 { 0216 Theme *editedTheme = mEditor->editedTheme(); 0217 if (!editedTheme) { 0218 return; 0219 } 0220 0221 mEditor->commit(); 0222 0223 ThemeListWidgetItem *editedItem = findThemeItemByTheme(editedTheme); 0224 if (!editedItem) { 0225 return; 0226 } 0227 0228 // We must reset the runtime column state as the columns might have 0229 // totally changed in the editor 0230 editedTheme->resetColumnState(); 0231 0232 QString goodName = uniqueNameForTheme(editedTheme->name(), editedTheme); 0233 editedTheme->setName(goodName); 0234 editedItem->setText(goodName); 0235 } 0236 0237 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::editedThemeNameChanged() 0238 { 0239 Theme *set = mEditor->editedTheme(); 0240 if (!set) { 0241 return; 0242 } 0243 0244 ThemeListWidgetItem *it = findThemeItemByTheme(set); 0245 if (!it) { 0246 return; 0247 } 0248 0249 QString goodName = uniqueNameForTheme(set->name(), set); 0250 0251 it->setText(goodName); 0252 } 0253 0254 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::fillThemeList() 0255 { 0256 const QMap<QString, Theme *> &sets = Manager::instance()->themes(); 0257 0258 QMap<QString, Theme *>::ConstIterator end(sets.constEnd()); 0259 for (QMap<QString, Theme *>::ConstIterator it = sets.constBegin(); it != end; ++it) { 0260 (void)new ThemeListWidgetItem(mThemeList, *(*it)); 0261 } 0262 } 0263 0264 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::themeListItemClicked(QListWidgetItem *cur) 0265 { 0266 commitEditor(); 0267 0268 const int numberOfSelectedItem(mThemeList->selectedItems().count()); 0269 0270 ThemeListWidgetItem *item = cur ? dynamic_cast<ThemeListWidgetItem *>(cur) : nullptr; 0271 mDeleteThemeButton->setEnabled(item && !item->theme()->readOnly()); 0272 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1); 0273 mEditor->editTheme(item ? item->theme() : nullptr); 0274 mExportThemeButton->setEnabled(numberOfSelectedItem > 0); 0275 0276 if (item && !item->isSelected()) { 0277 item->setSelected(true); // make sure it's true 0278 } 0279 } 0280 0281 ThemeListWidgetItem *ConfigureThemesDialog::ConfigureThemesDialogPrivate::findThemeItemById(const QString &themeId) 0282 { 0283 const int c = mThemeList->count(); 0284 int i = 0; 0285 while (i < c) { 0286 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i)); 0287 if (item) { 0288 if (item->theme()->id() == themeId) { 0289 return item; 0290 } 0291 } 0292 ++i; 0293 } 0294 return nullptr; 0295 } 0296 0297 ThemeListWidgetItem *ConfigureThemesDialog::ConfigureThemesDialogPrivate::findThemeItemByName(const QString &name, Theme *skipTheme) 0298 { 0299 const int c = mThemeList->count(); 0300 int i = 0; 0301 while (i < c) { 0302 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i)); 0303 if (item) { 0304 if (item->theme() != skipTheme) { 0305 if (item->theme()->name() == name) { 0306 return item; 0307 } 0308 } 0309 } 0310 ++i; 0311 } 0312 return nullptr; 0313 } 0314 0315 ThemeListWidgetItem *ConfigureThemesDialog::ConfigureThemesDialogPrivate::findThemeItemByTheme(Theme *set) 0316 { 0317 const int c = mThemeList->count(); 0318 int i = 0; 0319 while (i < c) { 0320 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->item(i)); 0321 if (item) { 0322 if (item->theme() == set) { 0323 return item; 0324 } 0325 } 0326 ++i; 0327 } 0328 return nullptr; 0329 } 0330 0331 QString ConfigureThemesDialog::ConfigureThemesDialogPrivate::uniqueNameForTheme(const QString &baseName, Theme *skipTheme) 0332 { 0333 QString ret = baseName; 0334 if (ret.isEmpty()) { 0335 ret = i18n("Unnamed Theme"); 0336 } 0337 0338 int idx = 1; 0339 0340 ThemeListWidgetItem *item = findThemeItemByName(ret, skipTheme); 0341 while (item) { 0342 idx++; 0343 ret = QStringLiteral("%1 %2").arg(baseName, QString::number(idx)); 0344 item = findThemeItemByName(ret, skipTheme); 0345 } 0346 return ret; 0347 } 0348 0349 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::newThemeButtonClicked() 0350 { 0351 const int numberOfSelectedItem(mThemeList->selectedItems().count()); 0352 Theme emptyTheme; 0353 emptyTheme.setName(uniqueNameForTheme(i18n("New Theme"))); 0354 auto col = new Theme::Column(); 0355 col->setLabel(i18n("New Column")); 0356 col->setVisibleByDefault(true); 0357 col->addMessageRow(new Theme::Row()); 0358 col->addGroupHeaderRow(new Theme::Row()); 0359 emptyTheme.addColumn(col); 0360 auto item = new ThemeListWidgetItem(mThemeList, emptyTheme); 0361 0362 mThemeList->clearSelection(); 0363 mThemeList->setCurrentItem(item); 0364 Core::Theme *theme = item->theme(); 0365 if (theme) { 0366 mEditor->editTheme(theme); 0367 0368 mDeleteThemeButton->setEnabled(!theme->readOnly()); 0369 mExportThemeButton->setEnabled(item); 0370 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1); 0371 } else { 0372 mDeleteThemeButton->setEnabled(false); 0373 mExportThemeButton->setEnabled(false); 0374 mCloneThemeButton->setEnabled(false); 0375 } 0376 } 0377 0378 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::cloneThemeButtonClicked() 0379 { 0380 auto item = dynamic_cast<ThemeListWidgetItem *>(mThemeList->currentItem()); 0381 if (!item) { 0382 return; 0383 } 0384 commitEditor(); 0385 item->setSelected(false); 0386 Theme copyTheme(*(item->theme())); 0387 copyTheme.setReadOnly(false); 0388 copyTheme.detach(); // detach shared data 0389 copyTheme.generateUniqueId(); // regenerate id so it becomes different 0390 copyTheme.setName(uniqueNameForTheme(item->theme()->name())); 0391 item = new ThemeListWidgetItem(mThemeList, copyTheme); 0392 0393 mThemeList->setCurrentItem(item); 0394 mEditor->editTheme(item->theme()); 0395 0396 const int numberOfSelectedItem(mThemeList->selectedItems().count()); 0397 mDeleteThemeButton->setEnabled(!item->theme()->readOnly()); 0398 mExportThemeButton->setEnabled(true); 0399 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1); 0400 } 0401 0402 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::deleteThemeButtonClicked() 0403 { 0404 const QList<QListWidgetItem *> list = mThemeList->selectedItems(); 0405 if (list.isEmpty()) { 0406 return; 0407 } 0408 const QString question = list.count() > 1 ? i18n("Do you want to delete selected themes?") : i18n("Do you want to delete \"%1\"?", list.first()->text()); 0409 const int answer = 0410 KMessageBox::questionTwoActions(q, question, i18nc("@title:window", "Delete Theme"), KStandardGuiItem::del(), KStandardGuiItem::cancel()); 0411 if (answer == KMessageBox::ButtonCode::PrimaryAction) { 0412 mEditor->editTheme(nullptr); // forget it 0413 for (QListWidgetItem *it : list) { 0414 auto item = dynamic_cast<ThemeListWidgetItem *>(it); 0415 if (!item) { 0416 return; 0417 } 0418 if (!item->theme()->readOnly()) { 0419 delete item; // this will trigger themeListCurrentItemChanged() 0420 } 0421 if (mThemeList->count() < 2) { 0422 break; // no way: desperately try to keep at least one option set alive :) 0423 } 0424 } 0425 0426 auto newItem = dynamic_cast<ThemeListWidgetItem *>(mThemeList->currentItem()); 0427 mDeleteThemeButton->setEnabled(newItem && !newItem->theme()->readOnly()); 0428 mExportThemeButton->setEnabled(newItem); 0429 const int numberOfSelectedItem(mThemeList->selectedItems().count()); 0430 mCloneThemeButton->setEnabled(numberOfSelectedItem == 1); 0431 } 0432 } 0433 0434 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::importThemeButtonClicked() 0435 { 0436 const QString filename = QFileDialog::getOpenFileName(q, i18n("Import Theme")); 0437 if (!filename.isEmpty()) { 0438 KConfig config(filename); 0439 0440 if (config.hasGroup(QStringLiteral("MessageListView::Themes"))) { 0441 KConfigGroup grp(&config, QStringLiteral("MessageListView::Themes")); 0442 const int cnt = grp.readEntry("Count", 0); 0443 int idx = 0; 0444 while (idx < cnt) { 0445 const QString data = grp.readEntry(QStringLiteral("Set%1").arg(idx), QString()); 0446 if (!data.isEmpty()) { 0447 auto set = new Theme(); 0448 if (set->loadFromString(data)) { 0449 set->setReadOnly(false); 0450 set->detach(); // detach shared data 0451 set->generateUniqueId(); // regenerate id so it becomes different 0452 set->setName(uniqueNameForTheme(set->name())); 0453 (void)new ThemeListWidgetItem(mThemeList, *set); 0454 } else { 0455 delete set; 0456 } 0457 } 0458 ++idx; 0459 } 0460 } 0461 } 0462 } 0463 0464 void ConfigureThemesDialog::ConfigureThemesDialogPrivate::exportThemeButtonClicked() 0465 { 0466 const QList<QListWidgetItem *> list = mThemeList->selectedItems(); 0467 if (list.isEmpty()) { 0468 return; 0469 } 0470 const QString filename = QFileDialog::getSaveFileName(q, i18n("Export Theme"), QString(), i18n("All Files (*)")); 0471 if (!filename.isEmpty()) { 0472 KConfig config(filename); 0473 0474 KConfigGroup grp(&config, QStringLiteral("MessageListView::Themes")); 0475 grp.writeEntry("Count", list.count()); 0476 0477 int idx = 0; 0478 for (QListWidgetItem *item : list) { 0479 auto themeItem = static_cast<ThemeListWidgetItem *>(item); 0480 grp.writeEntry(QStringLiteral("Set%1").arg(idx), themeItem->theme()->saveToString()); 0481 ++idx; 0482 } 0483 } 0484 } 0485 0486 #include "moc_configurethemesdialog.cpp"