GrantleeTheme

grantleethememanager.cpp
1/*
2 SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7#include "grantleethememanager.h"
8#include "grantleetheme_p.h"
9
10#include <KActionCollection>
11#include <KActionMenu>
12#include <KAuthorized>
13#include <KConfigGroup>
14#include <KDirWatch>
15#include <KLocalizedString>
16#include <KNSWidgets/Action>
17#include <KSharedConfig>
18#include <KToggleAction>
19#include <QAction>
20#include <QIcon>
21#include <QPointer>
22
23#include <QActionGroup>
24#include <QDir>
25#include <QDirIterator>
26#include <QStandardPaths>
27
28namespace GrantleeTheme
29{
30class ThemeManagerPrivate
31{
32public:
33 ThemeManagerPrivate(const QString &type,
34 const QString &desktopFileName,
36 const QString &relativePath,
37 const QString &configFileName,
38 ThemeManager *qq)
39 : applicationType(type)
40 , defaultDesktopFileName(desktopFileName)
41 , actionCollection(ac)
42 , q(qq)
43 {
44 watch = new KDirWatch(q);
45 initThemesDirectories(relativePath);
46 if (KAuthorized::authorize(QStringLiteral("ghns"))) {
47 downloadThemesAction = new KNSWidgets::Action(i18n("Download new Templates..."), configFileName, q);
48 if (actionCollection) {
49 actionCollection->addAction(QStringLiteral("download_header_themes"), downloadThemesAction);
50 }
51 separatorAction = new QAction(q);
52 separatorAction->setSeparator(true);
53 }
54
55 q->connect(watch, &KDirWatch::dirty, q, [this]() {
56 directoryChanged();
57 });
58 updateThemesPath(true);
59
60 // Migrate the old configuration format that only support mail and addressbook
61 // theming to the new generic format
62 KSharedConfig::Ptr config = KSharedConfig::openConfig();
63 if (config->hasGroup(QStringLiteral("GrantleeTheme"))) {
64 const KConfigGroup group = config->group(QStringLiteral("GrantleeTheme"));
65 const QString mailTheme = group.readEntry(QStringLiteral("grantleeMailThemeName"));
66 const QString addressbookTheme = group.readEntry(QStringLiteral("grantleeAddressBookThemeName"));
67
68 config->group(QStringLiteral("mail")).writeEntry(QStringLiteral("themeName"), mailTheme);
69 config->group(QStringLiteral("addressbook")).writeEntry(QStringLiteral("themeName"), addressbookTheme);
70
71 config->deleteGroup(QStringLiteral("GrantleeTheme"));
72 }
73 }
74
75 ~ThemeManagerPrivate()
76 {
77 removeActions();
78 themes.clear();
79 }
80
81 void directoryChanged()
82 {
83 updateThemesPath();
84 updateActionList();
85 Q_EMIT q->updateThemes();
86 }
87
88 void updateThemesPath(bool init = false)
89 {
90 if (!init) {
91 if (!themesDirectories.isEmpty()) {
92 for (const QString &directory : std::as_const(themesDirectories)) {
93 watch->removeDir(directory);
94 }
95 } else {
96 return;
97 }
98 }
99
100 // clear all previous theme information
101 themes.clear();
102
103 for (const QString &directory : std::as_const(themesDirectories)) {
105 QStringList alreadyLoadedThemeName;
106 while (dirIt.hasNext()) {
107 dirIt.next();
108 const QString dirName = dirIt.fileName();
109 GrantleeTheme::Theme theme = q->loadTheme(dirIt.filePath(), dirName, defaultDesktopFileName);
110 if (theme.isValid()) {
111 QString themeName = theme.name();
112 if (alreadyLoadedThemeName.contains(themeName)) {
113 int i = 2;
114 const QString originalName(theme.name());
115 while (alreadyLoadedThemeName.contains(themeName)) {
116 themeName = originalName + QStringLiteral(" (%1)").arg(i);
117 ++i;
118 }
119 theme.d->name = themeName;
120 }
121 alreadyLoadedThemeName << themeName;
122 auto it = themes.find(dirName);
123 if (it != themes.end()) {
124 (*it).addThemePath(dirIt.filePath());
125 } else {
126 themes.insert(dirName, theme);
127 }
128 }
129 }
130 watch->addDir(directory);
131 }
132
133 Q_EMIT q->themesChanged();
134 watch->startScan();
135 }
136
137 void removeActions()
138 {
139 if (!actionGroup || !menu) {
140 return;
141 }
142 for (KToggleAction *action : std::as_const(themesActionList)) {
143 actionGroup->removeAction(action);
144 menu->removeAction(action);
145 if (actionCollection) {
146 actionCollection->removeAction(action);
147 }
148 }
149 if (separatorAction) {
150 menu->removeAction(separatorAction);
151 if (downloadThemesAction) {
152 menu->removeAction(downloadThemesAction);
153 }
154 }
155 themesActionList.clear();
156 }
157
158 void updateActionList()
159 {
160 if (!actionGroup || !menu) {
161 return;
162 }
163 QString themeActivated;
164
165 QAction *selectedAction = actionGroup->checkedAction();
166 if (selectedAction) {
167 themeActivated = selectedAction->data().toString();
168 }
169
170 removeActions();
171
172 bool themeActivatedFound = false;
174 while (i.hasNext()) {
175 i.next();
176 GrantleeTheme::Theme theme = i.value();
177 auto act = new KToggleAction(theme.name(), q);
178 act->setToolTip(theme.description());
179 act->setData(theme.dirName());
180 if (theme.dirName() == themeActivated) {
181 act->setChecked(true);
182 themeActivatedFound = true;
183 }
184 themesActionList.append(act);
185 actionGroup->addAction(act);
186 menu->addAction(act);
187 q->connect(act, &KToggleAction::triggered, q, [this]() {
188 slotThemeSelected();
189 });
190 }
191 if (!themeActivatedFound) {
192 if (!themesActionList.isEmpty() && !themeActivated.isEmpty()) {
193 // Activate first item if we removed theme.
194 KToggleAction *act = themesActionList.at(0);
195 act->setChecked(true);
196 selectTheme(act);
197 }
198 }
199 if (separatorAction) {
200 menu->addAction(separatorAction);
201 if (downloadThemesAction) {
202 menu->addAction(downloadThemesAction);
203 }
204 }
205 }
206
207 void selectTheme(KToggleAction *act)
208 {
209 if (act) {
210 KSharedConfig::Ptr config = KSharedConfig::openConfig();
211 KConfigGroup group = config->group(applicationType);
212 group.writeEntry(QStringLiteral("themeName"), act->data().toString());
213 config->sync();
214 }
215 }
216
217 void slotThemeSelected()
218 {
219 if (q->sender()) {
220 auto act = qobject_cast<KToggleAction *>(q->sender());
221 selectTheme(act);
222 Q_EMIT q->grantleeThemeSelected();
223 }
224 }
225
226 KToggleAction *actionForTheme()
227 {
228 const KSharedConfig::Ptr config = KSharedConfig::openConfig();
229 const KConfigGroup group = config->group(applicationType);
230 const QString themeName = group.readEntry(QStringLiteral("themeName"), QStringLiteral("default"));
231
232 if (themeName.isEmpty()) {
233 return nullptr;
234 }
235 for (KToggleAction *act : std::as_const(themesActionList)) {
236 if (act->data().toString() == themeName) {
237 return static_cast<KToggleAction *>(act);
238 }
239 }
240 return nullptr;
241 }
242
243 void initThemesDirectories(const QString &themesRelativePath)
244 {
245 if (!themesRelativePath.isEmpty()) {
247 const QString localDirectory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + themesRelativePath;
248 themesDirectories.append(localDirectory);
249 }
250 }
251
252 QString applicationType;
253 QString defaultDesktopFileName;
254 QStringList themesDirectories;
256 QList<KToggleAction *> themesActionList;
257 KDirWatch *watch = nullptr;
258 QActionGroup *actionGroup = nullptr;
259 KActionMenu *menu = nullptr;
260 KActionCollection *const actionCollection;
261 QAction *separatorAction = nullptr;
262 KNSWidgets::Action *downloadThemesAction = nullptr;
263 ThemeManager *const q;
264};
265}
266
267using namespace GrantleeTheme;
268
269ThemeManager::ThemeManager(const QString &applicationType,
270 const QString &defaultDesktopFileName,
271 KActionCollection *actionCollection,
272 const QString &path,
273 const QString &configFileName,
274 QObject *parent)
275 : QObject(parent)
276 , d(new ThemeManagerPrivate(applicationType, defaultDesktopFileName, actionCollection, path, configFileName, this))
277{
278}
279
280ThemeManager::~ThemeManager() = default;
281
282QMap<QString, GrantleeTheme::Theme> ThemeManager::themes() const
283{
284 return d->themes;
285}
286
287void ThemeManager::setActionGroup(QActionGroup *actionGroup)
288{
289 if (d->actionGroup != actionGroup) {
290 d->removeActions();
291 d->actionGroup = actionGroup;
292 d->updateActionList();
293 }
294}
295
296KToggleAction *ThemeManager::actionForTheme()
297{
298 return d->actionForTheme();
299}
300
301void ThemeManager::setThemeMenu(KActionMenu *menu)
302{
303 if (d->menu != menu) {
304 d->menu = menu;
305 d->updateActionList();
306 }
307}
308
309QStringList ThemeManager::displayExtraVariables(const QString &themename) const
310{
312 while (i.hasNext()) {
313 i.next();
314 if (i.value().dirName() == themename) {
315 return i.value().displayExtraVariables();
316 }
317 }
318 return {};
319}
320
321GrantleeTheme::Theme ThemeManager::theme(const QString &themeName)
322{
323 return d->themes.value(themeName);
324}
325
326QString ThemeManager::pathFromThemes(const QString &themesRelativePath, const QString &themeName, const QString &defaultDesktopFileName)
327{
328 QStringList themesDirectories;
329 if (!themesRelativePath.isEmpty()) {
331 if (themesDirectories.count() < 2) {
332 // Make sure to add local directory
334 if (!themesDirectories.contains(localDirectory)) {
335 themesDirectories.append(localDirectory);
336 }
337 }
338 for (const QString &directory : std::as_const(themesDirectories)) {
340 while (dirIt.hasNext()) {
341 dirIt.next();
342 const QString dirName = dirIt.fileName();
343 GrantleeTheme::Theme theme = loadTheme(dirIt.filePath(), dirName, defaultDesktopFileName);
344 if (theme.isValid()) {
345 if (dirName == themeName) {
346 return theme.absolutePath();
347 }
348 }
349 }
350 }
351 }
352 return {};
353}
354
355GrantleeTheme::Theme ThemeManager::loadTheme(const QString &themePath, const QString &dirName, const QString &defaultDesktopFileName)
356{
357 const GrantleeTheme::Theme theme(themePath, dirName, defaultDesktopFileName);
358 return theme;
359}
360
361QString ThemeManager::configuredThemeName() const
362{
363 return configuredThemeName(d->applicationType);
364}
365
366QString ThemeManager::configuredThemeName(const QString &themeType)
367{
368 const KSharedConfig::Ptr config = KSharedConfig::openConfig();
369 const KConfigGroup grp = config->group(themeType);
370 return grp.readEntry(QStringLiteral("themeName"));
371}
372
373#include "moc_grantleethememanager.cpp"
The Theme class.
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
void removeAction(QAction *action)
void addAction(QAction *action)
static Q_INVOKABLE bool authorize(const QString &action)
void removeDir(const QString &path)
void startScan(bool notify=false, bool skippedToo=false)
void addDir(const QString &path, WatchModes watchModes=WatchDirOnly)
void dirty(const QString &path)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
QString path(const QString &relativePath)
void setChecked(bool)
QVariant data() const const
void setSeparator(bool b)
void triggered(bool checked)
QAction * addAction(QAction *action)
QAction * checkedAction() const const
void removeAction(QAction *action)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void clear()
qsizetype count() const const
bool isEmpty() const const
void clear()
iterator end()
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QObject * sender() const const
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.