WebUI: Minor optimizations to the login page
[qBittorrent.git] / src / gui / uithemesource.cpp
blobc4e4ef7f4c8799e69c8eb71720362839292c8f41
1 /*
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,
19 * USA.
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"
41 namespace
43 const qint64 FILE_MAX_SIZE = 1024 * 1024;
45 QJsonObject parseThemeConfig(const QByteArray &data)
47 if (data.isEmpty())
48 return {};
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);
56 return {};
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);
63 return {};
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()};
75 if (!color.isValid())
77 LogMsg(UIThemeSource::tr("Invalid color for ID \"%1\" is provided by theme")
78 .arg(colorNode.key()), Log::WARNING);
79 continue;
82 colors.insert(colorNode.key(), color);
85 return colors;
88 Path findIcon(const QString &iconId, const Path &dir)
90 const Path pathSvg = dir / Path(iconId + u".svg");
91 if (pathSvg.exists())
92 return pathSvg;
94 const Path pathPng = dir / Path(iconId + u".png");
95 if (pathPng.exists())
96 return pathPng;
98 return {};
102 DefaultThemeSource::DefaultThemeSource()
103 : m_defaultPath {u":"_s}
104 , m_userPath {specialFolderLocation(SpecialFolder::Config) / Path(u"themes/default"_s)}
105 , m_colors {defaultUIThemeColors()}
107 loadColors();
110 QByteArray DefaultThemeSource::readStyleSheet()
112 return {};
115 QColor DefaultThemeSource::getColor(const QString &colorId, const ColorMode colorMode) const
117 return (colorMode == ColorMode::Light)
118 ? m_colors.value(colorId).light : m_colors.value(colorId).dark;
121 Path DefaultThemeSource::getIconPath(const QString &iconId, const ColorMode colorMode) const
123 const Path iconsPath {u"icons"_s};
124 const Path lightModeIconsPath = iconsPath / Path(u"light"_s);
125 const Path darkModeIconsPath = iconsPath / Path(u"dark"_s);
127 if (colorMode == ColorMode::Dark)
129 if (const Path iconPath = findIcon(iconId, (m_userPath / darkModeIconsPath))
130 ; !iconPath.isEmpty())
132 return iconPath;
135 if (const Path iconPath = findIcon(iconId, (m_defaultPath / darkModeIconsPath))
136 ; !iconPath.isEmpty())
138 return iconPath;
141 else
143 if (const Path iconPath = findIcon(iconId, (m_userPath / lightModeIconsPath))
144 ; !iconPath.isEmpty())
146 return iconPath;
150 return findIcon(iconId, (m_defaultPath / iconsPath));
153 void DefaultThemeSource::loadColors()
155 const auto readResult = Utils::IO::readFile((m_userPath / Path(CONFIG_FILE_NAME)), FILE_MAX_SIZE, QIODevice::Text);
156 if (!readResult)
158 if (readResult.error().status != Utils::IO::ReadError::NotExist)
159 LogMsg(tr("Failed to load default theme colors. %1").arg(readResult.error().message), Log::WARNING);
161 return;
164 const QByteArray &configData = readResult.value();
165 if (configData.isEmpty())
166 return;
168 const QJsonObject config = parseThemeConfig(configData);
170 const QHash<QString, QColor> lightModeColorOverrides = colorsFromJSON(config.value(KEY_COLORS_LIGHT).toObject());
171 for (auto overridesIt = lightModeColorOverrides.cbegin(); overridesIt != lightModeColorOverrides.cend(); ++overridesIt)
173 auto it = m_colors.find(overridesIt.key());
174 if (it != m_colors.end())
175 it.value().light = overridesIt.value();
178 const QHash<QString, QColor> darkModeColorOverrides = colorsFromJSON(config.value(KEY_COLORS_DARK).toObject());
179 for (auto overridesIt = darkModeColorOverrides.cbegin(); overridesIt != darkModeColorOverrides.cend(); ++overridesIt)
181 auto it = m_colors.find(overridesIt.key());
182 if (it != m_colors.end())
183 it.value().dark = overridesIt.value();
187 CustomThemeSource::CustomThemeSource(const Path &themeRootPath)
188 : m_themeRootPath {themeRootPath}
190 loadColors();
193 QColor CustomThemeSource::getColor(const QString &colorId, const ColorMode colorMode) const
195 if (colorMode == ColorMode::Dark)
197 if (const QColor color = m_darkModeColors.value(colorId)
198 ; color.isValid())
200 return color;
204 if (const QColor color = m_colors.value(colorId)
205 ; color.isValid())
207 return color;
210 return defaultThemeSource()->getColor(colorId, colorMode);
213 Path CustomThemeSource::getIconPath(const QString &iconId, const ColorMode colorMode) const
215 const Path iconsPath {u"icons"_s};
216 const Path darkModeIconsPath = iconsPath / Path(u"dark"_s);
218 if (colorMode == ColorMode::Dark)
220 if (const Path iconPath = findIcon(iconId, (themeRootPath() / darkModeIconsPath))
221 ; !iconPath.isEmpty())
223 return iconPath;
227 if (const Path iconPath = findIcon(iconId, (themeRootPath() / iconsPath))
228 ; !iconPath.isEmpty())
230 return iconPath;
233 return defaultThemeSource()->getIconPath(iconId, colorMode);
236 QByteArray CustomThemeSource::readStyleSheet()
238 const auto readResult = Utils::IO::readFile((themeRootPath() / Path(STYLESHEET_FILE_NAME)), FILE_MAX_SIZE, QIODevice::Text);
239 if (!readResult)
241 if (readResult.error().status != Utils::IO::ReadError::NotExist)
242 LogMsg(tr("Failed to load custom theme style sheet. %1").arg(readResult.error().message), Log::WARNING);
244 return {};
247 return readResult.value();
250 DefaultThemeSource *CustomThemeSource::defaultThemeSource() const
252 return m_defaultThemeSource.get();
255 Path CustomThemeSource::themeRootPath() const
257 return m_themeRootPath;
260 void CustomThemeSource::loadColors()
262 const auto readResult = Utils::IO::readFile((themeRootPath() / Path(CONFIG_FILE_NAME)), FILE_MAX_SIZE, QIODevice::Text);
263 if (!readResult)
265 if (readResult.error().status != Utils::IO::ReadError::NotExist)
266 LogMsg(tr("Failed to load custom theme colors. %1").arg(readResult.error().message), Log::WARNING);
268 return;
271 const QByteArray &configData = readResult.value();
272 if (configData.isEmpty())
273 return;
275 const QJsonObject config = parseThemeConfig(configData);
277 m_colors.insert(colorsFromJSON(config.value(KEY_COLORS).toObject()));
278 m_darkModeColors.insert(colorsFromJSON(config.value(KEY_COLORS_DARK).toObject()));
281 FolderThemeSource::FolderThemeSource(const Path &folderPath)
282 : CustomThemeSource(folderPath)
283 , m_folder {folderPath}
287 QByteArray FolderThemeSource::readStyleSheet()
289 // Directory used by stylesheet to reference internal resources
290 // for example `icon: url(:/uitheme/file.svg)` will be expected to
291 // point to a file `file.svg` in root directory of CONFIG_FILE_NAME
292 const QString stylesheetResourcesDir = u":/uitheme"_s;
294 QByteArray styleSheetData = CustomThemeSource::readStyleSheet();
295 return styleSheetData.replace(stylesheetResourcesDir.toUtf8(), m_folder.data().toUtf8());
298 QRCThemeSource::QRCThemeSource()
299 : CustomThemeSource(Path(u":/uitheme"_s))