1/*
2 * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "thememanager.h"
18#include <QApplication>
19#include <QDirIterator>
20#include <QFile>
21#include <QJsonArray>
22#include <QJsonDocument>
23#include <QJsonObject>
24#include <QMetaEnum>
25#include <QMetaObject>
26#include "utils.h"
27#include "settings.h"
28#include "systemconsts.h"
29
30ThemeManager::ThemeManager(QObject *parent) : QObject(parent),
31 mUseCustomTheme(false)
32{
33
34}
35
36PAppTheme ThemeManager::theme(const QString &themeName)
37{
38 if (mUseCustomTheme)
39 prepareCustomeTheme();
40 PAppTheme appTheme = std::make_shared<AppTheme>();
41 QString themeDir;
42 if (mUseCustomTheme)
43 themeDir = pSettings->dirs().config(Settings::Dirs::DataType::Theme);
44 else
45 themeDir = pSettings->dirs().data(Settings::Dirs::DataType::Theme);
46 appTheme->load(QString("%1/%2.json").arg(themeDir, themeName));
47 return appTheme;
48}
49
50bool ThemeManager::useCustomTheme() const
51{
52 return mUseCustomTheme;
53}
54
55void ThemeManager::setUseCustomTheme(bool newUseCustomTheme)
56{
57 mUseCustomTheme = newUseCustomTheme;
58}
59
60void ThemeManager::prepareCustomeTheme()
61{
62
63 if (QFile(pSettings->dirs().config(Settings::Dirs::DataType::Theme)).exists())
64 return;
65 copyFolder(pSettings->dirs().data(Settings::Dirs::DataType::Theme),pSettings->dirs().config(Settings::Dirs::DataType::Theme));
66}
67
68QList<PAppTheme> ThemeManager::getThemes()
69{
70 if (mUseCustomTheme)
71 prepareCustomeTheme();
72
73 QList<PAppTheme> result;
74 QString themeDir;
75 if (mUseCustomTheme)
76 themeDir = pSettings->dirs().config(Settings::Dirs::DataType::Theme);
77 else
78 themeDir = pSettings->dirs().data(Settings::Dirs::DataType::Theme);
79 QDirIterator it(themeDir);
80 while (it.hasNext()) {
81 it.next();
82 QFileInfo fileInfo = it.fileInfo();
83 if (fileInfo.suffix().compare("json", PATH_SENSITIVITY)==0) {
84 try {
85 PAppTheme appTheme = std::make_shared<AppTheme>();
86 appTheme->load(fileInfo.absoluteFilePath());
87 result.append(appTheme);
88 } catch(FileError e) {
89 //just skip it
90 }
91 }
92 }
93 return result;
94}
95
96AppTheme::AppTheme(QObject *parent):QObject(parent)
97{
98
99}
100
101QColor AppTheme::color(ColorRole role) const
102{
103 return mColors.value(role,QColor());
104}
105
106QPalette AppTheme::palette() const
107{
108 QPalette pal = initialPalette();
109
110 const static struct {
111 ColorRole themeColor;
112 QPalette::ColorRole paletteColorRole;
113 QPalette::ColorGroup paletteColorGroup;
114 bool setColorRoleAsBrush;
115 } mapping[] = {
116 {ColorRole::PaletteWindow, QPalette::Window, QPalette::All, false},
117 {ColorRole::PaletteWindowDisabled, QPalette::Window, QPalette::Disabled, false},
118 {ColorRole::PaletteWindowText, QPalette::WindowText, QPalette::All, true},
119 {ColorRole::PaletteWindowTextDisabled, QPalette::WindowText, QPalette::Disabled, true},
120 {ColorRole::PaletteBase, QPalette::Base, QPalette::All, false},
121 {ColorRole::PaletteBaseDisabled, QPalette::Base, QPalette::Disabled, false},
122 {ColorRole::PaletteAlternateBase, QPalette::AlternateBase, QPalette::All, false},
123 {ColorRole::PaletteAlternateBaseDisabled, QPalette::AlternateBase, QPalette::Disabled, false},
124 {ColorRole::PaletteToolTipBase, QPalette::ToolTipBase, QPalette::All, true},
125 {ColorRole::PaletteToolTipBaseDisabled, QPalette::ToolTipBase, QPalette::Disabled, true},
126 {ColorRole::PaletteToolTipText, QPalette::ToolTipText, QPalette::All, false},
127 {ColorRole::PaletteToolTipTextDisabled, QPalette::ToolTipText, QPalette::Disabled, false},
128 {ColorRole::PaletteText, QPalette::Text, QPalette::All, true},
129 {ColorRole::PaletteTextDisabled, QPalette::Text, QPalette::Disabled, true},
130 {ColorRole::PaletteButton, QPalette::Button, QPalette::All, false},
131 {ColorRole::PaletteButtonDisabled, QPalette::Button, QPalette::Disabled, false},
132 {ColorRole::PaletteButtonText, QPalette::ButtonText, QPalette::All, true},
133 {ColorRole::PaletteButtonTextDisabled, QPalette::ButtonText, QPalette::Disabled, true},
134 {ColorRole::PaletteBrightText, QPalette::BrightText, QPalette::All, false},
135 {ColorRole::PaletteBrightTextDisabled, QPalette::BrightText, QPalette::Disabled, false},
136 {ColorRole::PaletteHighlight, QPalette::Highlight, QPalette::All, true},
137 {ColorRole::PaletteHighlightDisabled, QPalette::Highlight, QPalette::Disabled, true},
138 {ColorRole::PaletteHighlightedText, QPalette::HighlightedText, QPalette::All, true},
139 {ColorRole::PaletteHighlightedTextDisabled, QPalette::HighlightedText, QPalette::Disabled, true},
140 {ColorRole::PaletteLink, QPalette::Link, QPalette::All, false},
141 {ColorRole::PaletteLinkDisabled, QPalette::Link, QPalette::Disabled, false},
142 {ColorRole::PaletteLinkVisited, QPalette::LinkVisited, QPalette::All, false},
143 {ColorRole::PaletteLinkVisitedDisabled, QPalette::LinkVisited, QPalette::Disabled, false},
144 {ColorRole::PaletteLight, QPalette::Light, QPalette::All, false},
145 {ColorRole::PaletteLightDisabled, QPalette::Light, QPalette::Disabled, false},
146 {ColorRole::PaletteMidlight, QPalette::Midlight, QPalette::All, false},
147 {ColorRole::PaletteMidlightDisabled, QPalette::Midlight, QPalette::Disabled, false},
148 {ColorRole::PaletteDark, QPalette::Dark, QPalette::All, false},
149 {ColorRole::PaletteDarkDisabled, QPalette::Dark, QPalette::Disabled, false},
150 {ColorRole::PaletteMid, QPalette::Mid, QPalette::All, false},
151 {ColorRole::PaletteMidDisabled, QPalette::Mid, QPalette::Disabled, false},
152 {ColorRole::PaletteShadow, QPalette::Shadow, QPalette::All, false},
153 {ColorRole::PaletteShadowDisabled, QPalette::Shadow, QPalette::Disabled, false}
154 };
155
156 for (auto entry: mapping) {
157 const QColor themeColor = color(entry.themeColor);
158 // Use original color if color is not defined in theme.
159 if (themeColor.isValid()) {
160// if (entry.setColorRoleAsBrush)
161// // TODO: Find out why sometimes setBrush is used
162// pal.setBrush(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
163// else
164// pal.setColor(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
165 pal.setBrush(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
166 pal.setColor(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
167 }
168 }
169
170 return pal;
171}
172
173void AppTheme::load(const QString &filename)
174{
175 QFile file(filename);
176 if (!file.exists()) {
177 throw FileError(tr("Theme file '%1' doesn't exist!")
178 .arg(filename));
179 }
180 if (file.open(QFile::ReadOnly)) {
181 QByteArray content = file.readAll();
182 QJsonParseError error;
183 QJsonDocument doc(QJsonDocument::fromJson(content,&error));
184 if (error.error != QJsonParseError::NoError) {
185 throw FileError(tr("Error in json file '%1':%2 : %3")
186 .arg(filename)
187 .arg(error.offset)
188 .arg(error.errorString()));
189 }
190 QJsonObject obj=doc.object();
191 QFileInfo fileInfo(filename);
192 mName = fileInfo.baseName();
193 mDisplayName = obj["name"].toString();
194 QString localeName = obj["name_"+pSettings->environment().language()].toString();
195 if (!localeName.isEmpty())
196 mDisplayName = localeName;
197 mIsDark = obj["isDark"].toBool(false);
198 mDefaultColorScheme = obj["default scheme"].toString();
199 mDefaultIconSet = obj["default iconset"].toString();
200 QJsonObject colors = obj["palette"].toObject();
201 const QMetaObject &m = *metaObject();
202 QMetaEnum e = m.enumerator(m.indexOfEnumerator("ColorRole"));
203 for (int i = 0, total = e.keyCount(); i < total; ++i) {
204 const QString key = QLatin1String(e.key(i));
205 if (colors.contains(key)) {
206 QString val=colors[key].toString();
207 mColors.insert(i, QColor(val));
208 }
209 }
210
211 } else {
212 throw FileError(tr("Can't open the theme file '%1' for read.")
213 .arg(filename));
214 }
215}
216
217// If you copy QPalette, default values stay at default, even if that default is different
218// within the context of different widgets. Create deep copy.
219static QPalette copyPalette(const QPalette &p)
220{
221 QPalette res;
222 for (int group = 0; group < QPalette::NColorGroups; ++group) {
223 for (int role = 0; role < QPalette::NColorRoles; ++role) {
224 res.setBrush(QPalette::ColorGroup(group),
225 QPalette::ColorRole(role),
226 p.brush(QPalette::ColorGroup(group), QPalette::ColorRole(role)));
227 res.setColor(QPalette::ColorGroup(group),
228 QPalette::ColorRole(role),
229 p.color(QPalette::ColorGroup(group), QPalette::ColorRole(role)));
230 }
231 }
232 return res;
233}
234
235QPalette AppTheme::initialPalette()
236{
237 static QPalette palette = copyPalette(QApplication::palette());
238 return palette;
239}
240
241const QString &AppTheme::defaultIconSet() const
242{
243 return mDefaultIconSet;
244}
245
246void AppTheme::setDefaultIconSet(const QString &newDefaultIconSet)
247{
248 mDefaultIconSet = newDefaultIconSet;
249}
250
251const QString &AppTheme::name() const
252{
253 return mName;
254}
255
256const QString &AppTheme::displayName() const
257{
258 return mDisplayName;
259}
260
261const QString &AppTheme::defaultColorScheme() const
262{
263 return mDefaultColorScheme;
264}
265
266void AppTheme::setDefaultColorScheme(const QString &newDefaultColorScheme)
267{
268 mDefaultColorScheme = newDefaultColorScheme;
269}
270
271bool AppTheme::isDark() const
272{
273 return mIsDark;
274}
275