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"
33 #include <QJsonDocument>
34 #include <QJsonObject>
36 #include "base/global.h"
37 #include "base/logger.h"
38 #include "base/profile.h"
39 #include "base/utils/io.h"
43 const qint64 FILE_MAX_SIZE
= 1024 * 1024;
45 QJsonObject
parseThemeConfig(const QByteArray
&data
)
50 QJsonParseError jsonError
;
51 const QJsonDocument configJsonDoc
= QJsonDocument::fromJson(data
, &jsonError
);
52 if (jsonError
.error
!= QJsonParseError::NoError
)
54 LogMsg(UIThemeSource::tr("Couldn't parse UI Theme configuration file. Reason: %1")
55 .arg(jsonError
.errorString()), Log::WARNING
);
59 if (!configJsonDoc
.isObject())
61 LogMsg(UIThemeSource::tr("UI Theme configuration file has invalid format. Reason: %1")
62 .arg(UIThemeSource::tr("Root JSON value is not an object")), Log::WARNING
);
66 return configJsonDoc
.object();
69 QHash
<QString
, QColor
> colorsFromJSON(const QJsonObject
&jsonObj
)
71 QHash
<QString
, QColor
> colors
;
72 for (auto colorNode
= jsonObj
.constBegin(); colorNode
!= jsonObj
.constEnd(); ++colorNode
)
74 const QColor color
{colorNode
.value().toString()};
77 LogMsg(UIThemeSource::tr("Invalid color for ID \"%1\" is provided by theme")
78 .arg(colorNode
.key()), Log::WARNING
);
82 colors
.insert(colorNode
.key(), color
);
88 Path
findIcon(const QString
&iconId
, const Path
&dir
)
90 const Path pathSvg
= dir
/ Path(iconId
+ u
".svg");
94 const Path pathPng
= dir
/ Path(iconId
+ u
".png");
102 DefaultThemeSource::DefaultThemeSource()
103 : m_defaultPath
{u
":"_s
}
104 , m_userPath
{specialFolderLocation(SpecialFolder::Config
) / Path(u
"themes/default"_s
)}
105 , m_colors
{defaultUIThemeColors()}
110 QByteArray
DefaultThemeSource::readStyleSheet()
115 QColor
DefaultThemeSource::getColor(const QString
&colorId
, const ColorMode colorMode
) const
117 const auto iter
= m_colors
.constFind(colorId
);
118 Q_ASSERT(iter
!= m_colors
.constEnd());
119 if (iter
== m_colors
.constEnd()) [[unlikely
]]
122 return (colorMode
== ColorMode::Light
)
123 ? iter
.value().light
: iter
.value().dark
;
126 Path
DefaultThemeSource::getIconPath(const QString
&iconId
, const ColorMode colorMode
) const
128 const Path iconsPath
{u
"icons"_s
};
129 const Path lightModeIconsPath
= iconsPath
/ Path(u
"light"_s
);
130 const Path darkModeIconsPath
= iconsPath
/ Path(u
"dark"_s
);
132 if (colorMode
== ColorMode::Dark
)
134 if (const Path iconPath
= findIcon(iconId
, (m_userPath
/ darkModeIconsPath
))
135 ; !iconPath
.isEmpty())
140 if (const Path iconPath
= findIcon(iconId
, (m_defaultPath
/ darkModeIconsPath
))
141 ; !iconPath
.isEmpty())
148 if (const Path iconPath
= findIcon(iconId
, (m_userPath
/ lightModeIconsPath
))
149 ; !iconPath
.isEmpty())
155 return findIcon(iconId
, (m_defaultPath
/ iconsPath
));
158 void DefaultThemeSource::loadColors()
160 const auto readResult
= Utils::IO::readFile((m_userPath
/ Path(CONFIG_FILE_NAME
)), FILE_MAX_SIZE
, QIODevice::Text
);
163 if (readResult
.error().status
!= Utils::IO::ReadError::NotExist
)
164 LogMsg(tr("Failed to load default theme colors. %1").arg(readResult
.error().message
), Log::WARNING
);
169 const QByteArray
&configData
= readResult
.value();
170 if (configData
.isEmpty())
173 const QJsonObject config
= parseThemeConfig(configData
);
175 const QHash
<QString
, QColor
> lightModeColorOverrides
= colorsFromJSON(config
.value(KEY_COLORS_LIGHT
).toObject());
176 for (auto overridesIt
= lightModeColorOverrides
.cbegin(); overridesIt
!= lightModeColorOverrides
.cend(); ++overridesIt
)
178 auto it
= m_colors
.find(overridesIt
.key());
179 if (it
!= m_colors
.end())
180 it
.value().light
= overridesIt
.value();
183 const QHash
<QString
, QColor
> darkModeColorOverrides
= colorsFromJSON(config
.value(KEY_COLORS_DARK
).toObject());
184 for (auto overridesIt
= darkModeColorOverrides
.cbegin(); overridesIt
!= darkModeColorOverrides
.cend(); ++overridesIt
)
186 auto it
= m_colors
.find(overridesIt
.key());
187 if (it
!= m_colors
.end())
188 it
.value().dark
= overridesIt
.value();
192 CustomThemeSource::CustomThemeSource(const Path
&themeRootPath
)
193 : m_themeRootPath
{themeRootPath
}
198 QColor
CustomThemeSource::getColor(const QString
&colorId
, const ColorMode colorMode
) const
200 if (colorMode
== ColorMode::Dark
)
202 if (const QColor color
= m_darkModeColors
.value(colorId
)
209 if (const QColor color
= m_colors
.value(colorId
)
215 return defaultThemeSource()->getColor(colorId
, colorMode
);
218 Path
CustomThemeSource::getIconPath(const QString
&iconId
, const ColorMode colorMode
) const
220 const Path iconsPath
{u
"icons"_s
};
221 const Path darkModeIconsPath
= iconsPath
/ Path(u
"dark"_s
);
223 if (colorMode
== ColorMode::Dark
)
225 if (const Path iconPath
= findIcon(iconId
, (themeRootPath() / darkModeIconsPath
))
226 ; !iconPath
.isEmpty())
232 if (const Path iconPath
= findIcon(iconId
, (themeRootPath() / iconsPath
))
233 ; !iconPath
.isEmpty())
238 return defaultThemeSource()->getIconPath(iconId
, colorMode
);
241 QByteArray
CustomThemeSource::readStyleSheet()
243 const auto readResult
= Utils::IO::readFile((themeRootPath() / Path(STYLESHEET_FILE_NAME
)), FILE_MAX_SIZE
, QIODevice::Text
);
246 if (readResult
.error().status
!= Utils::IO::ReadError::NotExist
)
247 LogMsg(tr("Failed to load custom theme style sheet. %1").arg(readResult
.error().message
), Log::WARNING
);
252 return readResult
.value();
255 DefaultThemeSource
*CustomThemeSource::defaultThemeSource() const
257 return m_defaultThemeSource
.get();
260 Path
CustomThemeSource::themeRootPath() const
262 return m_themeRootPath
;
265 void CustomThemeSource::loadColors()
267 const auto readResult
= Utils::IO::readFile((themeRootPath() / Path(CONFIG_FILE_NAME
)), FILE_MAX_SIZE
, QIODevice::Text
);
270 if (readResult
.error().status
!= Utils::IO::ReadError::NotExist
)
271 LogMsg(tr("Failed to load custom theme colors. %1").arg(readResult
.error().message
), Log::WARNING
);
276 const QByteArray
&configData
= readResult
.value();
277 if (configData
.isEmpty())
280 const QJsonObject config
= parseThemeConfig(configData
);
282 m_colors
.insert(colorsFromJSON(config
.value(KEY_COLORS
).toObject()));
283 m_darkModeColors
.insert(colorsFromJSON(config
.value(KEY_COLORS_DARK
).toObject()));
286 FolderThemeSource::FolderThemeSource(const Path
&folderPath
)
287 : CustomThemeSource(folderPath
)
288 , m_folder
{folderPath
}
292 QByteArray
FolderThemeSource::readStyleSheet()
294 // Directory used by stylesheet to reference internal resources
295 // for example `icon: url(:/uitheme/file.svg)` will be expected to
296 // point to a file `file.svg` in root directory of CONFIG_FILE_NAME
297 const QString stylesheetResourcesDir
= u
":/uitheme"_s
;
299 QByteArray styleSheetData
= CustomThemeSource::readStyleSheet();
300 return styleSheetData
.replace(stylesheetResourcesDir
.toUtf8(), m_folder
.data().toUtf8());
303 QRCThemeSource::QRCThemeSource()
304 : CustomThemeSource(Path(u
":/uitheme"_s
))