2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2019, 2021 Prince Gupta <jagannatharjun11@gmail.com>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If
26 * you modify file(s), you may extend this exception to your version of the
27 * file(s), but you are not obligated to do so. If you do not wish to do so,
28 * delete this exception statement from your version.
31 #include "uithemesource.h"
34 #include <QJsonDocument>
35 #include <QJsonObject>
37 #include "base/global.h"
38 #include "base/logger.h"
39 #include "base/profile.h"
43 QByteArray
readFile(const Path
&filePath
)
45 QFile file
{filePath
.data()};
49 if (file
.open(QIODevice::ReadOnly
| QIODevice::Text
))
50 return file
.readAll();
52 LogMsg(UIThemeSource::tr("UITheme - Failed to open \"%1\". Reason: %2")
53 .arg(filePath
.filename(), file
.errorString())
58 QJsonObject
parseThemeConfig(const QByteArray
&data
)
63 QJsonParseError jsonError
;
64 const QJsonDocument configJsonDoc
= QJsonDocument::fromJson(data
, &jsonError
);
65 if (jsonError
.error
!= QJsonParseError::NoError
)
67 LogMsg(UIThemeSource::tr("Couldn't parse UI Theme configuration file. Reason: %1")
68 .arg(jsonError
.errorString()), Log::WARNING
);
72 if (!configJsonDoc
.isObject())
74 LogMsg(UIThemeSource::tr("UI Theme configuration file has invalid format. Reason: %1")
75 .arg(UIThemeSource::tr("Root JSON value is not an object")), Log::WARNING
);
79 return configJsonDoc
.object();
82 QHash
<QString
, QColor
> colorsFromJSON(const QJsonObject
&jsonObj
)
84 QHash
<QString
, QColor
> colors
;
85 for (auto colorNode
= jsonObj
.constBegin(); colorNode
!= jsonObj
.constEnd(); ++colorNode
)
87 const QColor color
{colorNode
.value().toString()};
90 LogMsg(UIThemeSource::tr("Invalid color for ID \"%1\" is provided by theme")
91 .arg(colorNode
.key()), Log::WARNING
);
95 colors
.insert(colorNode
.key(), color
);
101 Path
findIcon(const QString
&iconId
, const Path
&dir
)
103 const Path pathSvg
= dir
/ Path(iconId
+ u
".svg");
104 if (pathSvg
.exists())
107 const Path pathPng
= dir
/ Path(iconId
+ u
".png");
108 if (pathPng
.exists())
115 DefaultThemeSource::DefaultThemeSource()
116 : m_defaultPath
{u
":"_qs
}
117 , m_userPath
{specialFolderLocation(SpecialFolder::Config
) / Path(u
"themes/default"_qs
)}
118 , m_colors
{defaultUIThemeColors()}
123 QByteArray
DefaultThemeSource::readStyleSheet()
128 QColor
DefaultThemeSource::getColor(const QString
&colorId
, const ColorMode colorMode
) const
130 return (colorMode
== ColorMode::Light
)
131 ? m_colors
.value(colorId
).light
: m_colors
.value(colorId
).dark
;
134 Path
DefaultThemeSource::getIconPath(const QString
&iconId
, const ColorMode colorMode
) const
136 const Path iconsPath
{u
"icons"_qs
};
137 const Path lightModeIconsPath
= iconsPath
/ Path(u
"light"_qs
);
138 const Path darkModeIconsPath
= iconsPath
/ Path(u
"dark"_qs
);
140 if (colorMode
== ColorMode::Dark
)
142 if (const Path iconPath
= findIcon(iconId
, (m_userPath
/ darkModeIconsPath
))
143 ; !iconPath
.isEmpty())
148 if (const Path iconPath
= findIcon(iconId
, (m_defaultPath
/ darkModeIconsPath
))
149 ; !iconPath
.isEmpty())
156 if (const Path iconPath
= findIcon(iconId
, (m_userPath
/ lightModeIconsPath
))
157 ; !iconPath
.isEmpty())
163 return findIcon(iconId
, (m_defaultPath
/ iconsPath
));
166 void DefaultThemeSource::loadColors()
168 const QByteArray configData
= readFile(m_userPath
/ Path(CONFIG_FILE_NAME
));
169 if (configData
.isEmpty())
172 const QJsonObject config
= parseThemeConfig(configData
);
174 QHash
<QString
, QColor
> lightModeColorOverrides
= colorsFromJSON(config
.value(KEY_COLORS_LIGHT
).toObject());
175 for (auto overridesIt
= lightModeColorOverrides
.cbegin(); overridesIt
!= lightModeColorOverrides
.cend(); ++overridesIt
)
177 auto it
= m_colors
.find(overridesIt
.key());
178 if (it
!= m_colors
.end())
179 it
.value().light
= overridesIt
.value();
182 QHash
<QString
, QColor
> darkModeColorOverrides
= colorsFromJSON(config
.value(KEY_COLORS_DARK
).toObject());
183 for (auto overridesIt
= darkModeColorOverrides
.cbegin(); overridesIt
!= darkModeColorOverrides
.cend(); ++overridesIt
)
185 auto it
= m_colors
.find(overridesIt
.key());
186 if (it
!= m_colors
.end())
187 it
.value().dark
= overridesIt
.value();
191 QColor
CustomThemeSource::getColor(const QString
&colorId
, const ColorMode colorMode
) const
193 if (colorMode
== ColorMode::Dark
)
195 if (const QColor color
= m_darkModeColors
.value(colorId
)
202 if (const QColor color
= m_colors
.value(colorId
)
208 return defaultThemeSource()->getColor(colorId
, colorMode
);
211 Path
CustomThemeSource::getIconPath(const QString
&iconId
, const ColorMode colorMode
) const
213 const Path iconsPath
{u
"icons"_qs
};
214 const Path darkModeIconsPath
= iconsPath
/ Path(u
"dark"_qs
);
216 if (colorMode
== ColorMode::Dark
)
218 if (const Path iconPath
= findIcon(iconId
, (themeRootPath() / darkModeIconsPath
))
219 ; !iconPath
.isEmpty())
225 if (const Path iconPath
= findIcon(iconId
, (themeRootPath() / iconsPath
))
226 ; !iconPath
.isEmpty())
231 return defaultThemeSource()->getIconPath(iconId
, colorMode
);
234 QByteArray
CustomThemeSource::readStyleSheet()
236 return readFile(themeRootPath() / Path(STYLESHEET_FILE_NAME
));
239 DefaultThemeSource
*CustomThemeSource::defaultThemeSource() const
241 return m_defaultThemeSource
.get();
244 void CustomThemeSource::loadColors()
246 const QByteArray configData
= readFile(themeRootPath() / Path(CONFIG_FILE_NAME
));
247 if (configData
.isEmpty())
250 const QJsonObject config
= parseThemeConfig(configData
);
252 m_colors
.insert(colorsFromJSON(config
.value(KEY_COLORS
).toObject()));
253 m_darkModeColors
.insert(colorsFromJSON(config
.value(KEY_COLORS_DARK
).toObject()));
256 Path
QRCThemeSource::themeRootPath() const
258 return Path(u
":/uitheme"_qs
);
261 FolderThemeSource::FolderThemeSource(const Path
&folderPath
)
262 : m_folder
{folderPath
}
266 QByteArray
FolderThemeSource::readStyleSheet()
268 // Directory used by stylesheet to reference internal resources
269 // for example `icon: url(:/uitheme/file.svg)` will be expected to
270 // point to a file `file.svg` in root directory of CONFIG_FILE_NAME
271 const QString stylesheetResourcesDir
= u
":/uitheme"_qs
;
273 QByteArray styleSheetData
= CustomThemeSource::readStyleSheet();
274 return styleSheetData
.replace(stylesheetResourcesDir
.toUtf8(), themeRootPath().data().toUtf8());
277 Path
FolderThemeSource::themeRootPath() const