File indexing completed on 2024-05-12 05:13:33

0001 /*
0002   SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "grantleethememanager.h"
0008 #include "grantleetheme_p.h"
0009 
0010 #include <KActionCollection>
0011 #include <KActionMenu>
0012 #include <KAuthorized>
0013 #include <KConfigGroup>
0014 #include <KDirWatch>
0015 #include <KLocalizedString>
0016 #include <KNSWidgets/Action>
0017 #include <KSharedConfig>
0018 #include <KToggleAction>
0019 #include <QAction>
0020 #include <QIcon>
0021 #include <QPointer>
0022 
0023 #include <QActionGroup>
0024 #include <QDir>
0025 #include <QDirIterator>
0026 #include <QStandardPaths>
0027 
0028 namespace GrantleeTheme
0029 {
0030 class ThemeManagerPrivate
0031 {
0032 public:
0033     ThemeManagerPrivate(const QString &type,
0034                         const QString &desktopFileName,
0035                         KActionCollection *ac,
0036                         const QString &relativePath,
0037                         const QString &configFileName,
0038                         ThemeManager *qq)
0039         : applicationType(type)
0040         , defaultDesktopFileName(desktopFileName)
0041         , actionCollection(ac)
0042         , q(qq)
0043     {
0044         watch = new KDirWatch(q);
0045         initThemesDirectories(relativePath);
0046         if (KAuthorized::authorize(QStringLiteral("ghns"))) {
0047             downloadThemesAction = new KNSWidgets::Action(i18n("Download new Templates..."), configFileName, q);
0048             if (actionCollection) {
0049                 actionCollection->addAction(QStringLiteral("download_header_themes"), downloadThemesAction);
0050             }
0051             separatorAction = new QAction(q);
0052             separatorAction->setSeparator(true);
0053         }
0054 
0055         q->connect(watch, &KDirWatch::dirty, q, [this]() {
0056             directoryChanged();
0057         });
0058         updateThemesPath(true);
0059 
0060         // Migrate the old configuration format that only support mail and addressbook
0061         // theming to the new generic format
0062         KSharedConfig::Ptr config = KSharedConfig::openConfig();
0063         if (config->hasGroup(QStringLiteral("GrantleeTheme"))) {
0064             const KConfigGroup group = config->group(QStringLiteral("GrantleeTheme"));
0065             const QString mailTheme = group.readEntry(QStringLiteral("grantleeMailThemeName"));
0066             const QString addressbookTheme = group.readEntry(QStringLiteral("grantleeAddressBookThemeName"));
0067 
0068             config->group(QStringLiteral("mail")).writeEntry(QStringLiteral("themeName"), mailTheme);
0069             config->group(QStringLiteral("addressbook")).writeEntry(QStringLiteral("themeName"), addressbookTheme);
0070 
0071             config->deleteGroup(QStringLiteral("GrantleeTheme"));
0072         }
0073     }
0074 
0075     ~ThemeManagerPrivate()
0076     {
0077         removeActions();
0078         themes.clear();
0079     }
0080 
0081     void directoryChanged()
0082     {
0083         updateThemesPath();
0084         updateActionList();
0085         Q_EMIT q->updateThemes();
0086     }
0087 
0088     void updateThemesPath(bool init = false)
0089     {
0090         if (!init) {
0091             if (!themesDirectories.isEmpty()) {
0092                 for (const QString &directory : std::as_const(themesDirectories)) {
0093                     watch->removeDir(directory);
0094                 }
0095             } else {
0096                 return;
0097             }
0098         }
0099 
0100         // clear all previous theme information
0101         themes.clear();
0102 
0103         for (const QString &directory : std::as_const(themesDirectories)) {
0104             QDirIterator dirIt(directory, QStringList(), QDir::AllDirs | QDir::NoDotAndDotDot);
0105             QStringList alreadyLoadedThemeName;
0106             while (dirIt.hasNext()) {
0107                 dirIt.next();
0108                 const QString dirName = dirIt.fileName();
0109                 GrantleeTheme::Theme theme = q->loadTheme(dirIt.filePath(), dirName, defaultDesktopFileName);
0110                 if (theme.isValid()) {
0111                     QString themeName = theme.name();
0112                     if (alreadyLoadedThemeName.contains(themeName)) {
0113                         int i = 2;
0114                         const QString originalName(theme.name());
0115                         while (alreadyLoadedThemeName.contains(themeName)) {
0116                             themeName = originalName + QStringLiteral(" (%1)").arg(i);
0117                             ++i;
0118                         }
0119                         theme.d->name = themeName;
0120                     }
0121                     alreadyLoadedThemeName << themeName;
0122                     auto it = themes.find(dirName);
0123                     if (it != themes.end()) {
0124                         (*it).addThemePath(dirIt.filePath());
0125                     } else {
0126                         themes.insert(dirName, theme);
0127                     }
0128                 }
0129             }
0130             watch->addDir(directory);
0131         }
0132 
0133         Q_EMIT q->themesChanged();
0134         watch->startScan();
0135     }
0136 
0137     void removeActions()
0138     {
0139         if (!actionGroup || !menu) {
0140             return;
0141         }
0142         for (KToggleAction *action : std::as_const(themesActionList)) {
0143             actionGroup->removeAction(action);
0144             menu->removeAction(action);
0145             if (actionCollection) {
0146                 actionCollection->removeAction(action);
0147             }
0148         }
0149         if (separatorAction) {
0150             menu->removeAction(separatorAction);
0151             if (downloadThemesAction) {
0152                 menu->removeAction(downloadThemesAction);
0153             }
0154         }
0155         themesActionList.clear();
0156     }
0157 
0158     void updateActionList()
0159     {
0160         if (!actionGroup || !menu) {
0161             return;
0162         }
0163         QString themeActivated;
0164 
0165         QAction *selectedAction = actionGroup->checkedAction();
0166         if (selectedAction) {
0167             themeActivated = selectedAction->data().toString();
0168         }
0169 
0170         removeActions();
0171 
0172         bool themeActivatedFound = false;
0173         QMapIterator<QString, GrantleeTheme::Theme> i(themes);
0174         while (i.hasNext()) {
0175             i.next();
0176             GrantleeTheme::Theme theme = i.value();
0177             auto act = new KToggleAction(theme.name(), q);
0178             act->setToolTip(theme.description());
0179             act->setData(theme.dirName());
0180             if (theme.dirName() == themeActivated) {
0181                 act->setChecked(true);
0182                 themeActivatedFound = true;
0183             }
0184             themesActionList.append(act);
0185             actionGroup->addAction(act);
0186             menu->addAction(act);
0187             q->connect(act, &KToggleAction::triggered, q, [this]() {
0188                 slotThemeSelected();
0189             });
0190         }
0191         if (!themeActivatedFound) {
0192             if (!themesActionList.isEmpty() && !themeActivated.isEmpty()) {
0193                 // Activate first item if we removed theme.
0194                 KToggleAction *act = themesActionList.at(0);
0195                 act->setChecked(true);
0196                 selectTheme(act);
0197             }
0198         }
0199         if (separatorAction) {
0200             menu->addAction(separatorAction);
0201             if (downloadThemesAction) {
0202                 menu->addAction(downloadThemesAction);
0203             }
0204         }
0205     }
0206 
0207     void selectTheme(KToggleAction *act)
0208     {
0209         if (act) {
0210             KSharedConfig::Ptr config = KSharedConfig::openConfig();
0211             KConfigGroup group = config->group(applicationType);
0212             group.writeEntry(QStringLiteral("themeName"), act->data().toString());
0213             config->sync();
0214         }
0215     }
0216 
0217     void slotThemeSelected()
0218     {
0219         if (q->sender()) {
0220             auto act = qobject_cast<KToggleAction *>(q->sender());
0221             selectTheme(act);
0222             Q_EMIT q->grantleeThemeSelected();
0223         }
0224     }
0225 
0226     KToggleAction *actionForTheme()
0227     {
0228         const KSharedConfig::Ptr config = KSharedConfig::openConfig();
0229         const KConfigGroup group = config->group(applicationType);
0230         const QString themeName = group.readEntry(QStringLiteral("themeName"), QStringLiteral("default"));
0231 
0232         if (themeName.isEmpty()) {
0233             return nullptr;
0234         }
0235         for (KToggleAction *act : std::as_const(themesActionList)) {
0236             if (act->data().toString() == themeName) {
0237                 return static_cast<KToggleAction *>(act);
0238             }
0239         }
0240         return nullptr;
0241     }
0242 
0243     void initThemesDirectories(const QString &themesRelativePath)
0244     {
0245         if (!themesRelativePath.isEmpty()) {
0246             themesDirectories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, themesRelativePath, QStandardPaths::LocateDirectory);
0247             const QString localDirectory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + themesRelativePath;
0248             themesDirectories.append(localDirectory);
0249         }
0250     }
0251 
0252     QString applicationType;
0253     QString defaultDesktopFileName;
0254     QStringList themesDirectories;
0255     QMap<QString, GrantleeTheme::Theme> themes;
0256     QList<KToggleAction *> themesActionList;
0257     KDirWatch *watch = nullptr;
0258     QActionGroup *actionGroup = nullptr;
0259     KActionMenu *menu = nullptr;
0260     KActionCollection *const actionCollection;
0261     QAction *separatorAction = nullptr;
0262     KNSWidgets::Action *downloadThemesAction = nullptr;
0263     ThemeManager *const q;
0264 };
0265 }
0266 
0267 using namespace GrantleeTheme;
0268 
0269 ThemeManager::ThemeManager(const QString &applicationType,
0270                            const QString &defaultDesktopFileName,
0271                            KActionCollection *actionCollection,
0272                            const QString &path,
0273                            const QString &configFileName,
0274                            QObject *parent)
0275     : QObject(parent)
0276     , d(new ThemeManagerPrivate(applicationType, defaultDesktopFileName, actionCollection, path, configFileName, this))
0277 {
0278 }
0279 
0280 ThemeManager::~ThemeManager() = default;
0281 
0282 QMap<QString, GrantleeTheme::Theme> ThemeManager::themes() const
0283 {
0284     return d->themes;
0285 }
0286 
0287 void ThemeManager::setActionGroup(QActionGroup *actionGroup)
0288 {
0289     if (d->actionGroup != actionGroup) {
0290         d->removeActions();
0291         d->actionGroup = actionGroup;
0292         d->updateActionList();
0293     }
0294 }
0295 
0296 KToggleAction *ThemeManager::actionForTheme()
0297 {
0298     return d->actionForTheme();
0299 }
0300 
0301 void ThemeManager::setThemeMenu(KActionMenu *menu)
0302 {
0303     if (d->menu != menu) {
0304         d->menu = menu;
0305         d->updateActionList();
0306     }
0307 }
0308 
0309 QStringList ThemeManager::displayExtraVariables(const QString &themename) const
0310 {
0311     QMapIterator<QString, GrantleeTheme::Theme> i(d->themes);
0312     while (i.hasNext()) {
0313         i.next();
0314         if (i.value().dirName() == themename) {
0315             return i.value().displayExtraVariables();
0316         }
0317     }
0318     return {};
0319 }
0320 
0321 GrantleeTheme::Theme ThemeManager::theme(const QString &themeName)
0322 {
0323     return d->themes.value(themeName);
0324 }
0325 
0326 QString ThemeManager::pathFromThemes(const QString &themesRelativePath, const QString &themeName, const QString &defaultDesktopFileName)
0327 {
0328     QStringList themesDirectories;
0329     if (!themesRelativePath.isEmpty()) {
0330         themesDirectories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, themesRelativePath, QStandardPaths::LocateDirectory);
0331         if (themesDirectories.count() < 2) {
0332             // Make sure to add local directory
0333             const QString localDirectory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + themesRelativePath;
0334             if (!themesDirectories.contains(localDirectory)) {
0335                 themesDirectories.append(localDirectory);
0336             }
0337         }
0338         for (const QString &directory : std::as_const(themesDirectories)) {
0339             QDirIterator dirIt(directory, QStringList(), QDir::AllDirs | QDir::NoDotAndDotDot);
0340             while (dirIt.hasNext()) {
0341                 dirIt.next();
0342                 const QString dirName = dirIt.fileName();
0343                 GrantleeTheme::Theme theme = loadTheme(dirIt.filePath(), dirName, defaultDesktopFileName);
0344                 if (theme.isValid()) {
0345                     if (dirName == themeName) {
0346                         return theme.absolutePath();
0347                     }
0348                 }
0349             }
0350         }
0351     }
0352     return {};
0353 }
0354 
0355 GrantleeTheme::Theme ThemeManager::loadTheme(const QString &themePath, const QString &dirName, const QString &defaultDesktopFileName)
0356 {
0357     const GrantleeTheme::Theme theme(themePath, dirName, defaultDesktopFileName);
0358     return theme;
0359 }
0360 
0361 QString ThemeManager::configuredThemeName() const
0362 {
0363     return configuredThemeName(d->applicationType);
0364 }
0365 
0366 QString ThemeManager::configuredThemeName(const QString &themeType)
0367 {
0368     const KSharedConfig::Ptr config = KSharedConfig::openConfig();
0369     const KConfigGroup grp = config->group(themeType);
0370     return grp.readEntry(QStringLiteral("themeName"));
0371 }
0372 
0373 #include "moc_grantleethememanager.cpp"