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"