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"