WebUI: Use Map instead of Mootools Hash in Torrents table
[qBittorrent.git] / src / gui / uithemesource.cpp
blobbe0138ee00943d25e7e2d5883587c1baff7e9db8
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 const auto iter = m_colors.constFind(colorId);
118 Q_ASSERT(iter != m_colors.constEnd());
119 if (iter == m_colors.constEnd()) [[unlikely]]
120 return {};
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())
137 return iconPath;
140 if (const Path iconPath = findIcon(iconId, (m_defaultPath / darkModeIconsPath))
141 ; !iconPath.isEmpty())
143 return iconPath;
146 else
148 if (const Path iconPath = findIcon(iconId, (m_userPath / lightModeIconsPath))
149 ; !iconPath.isEmpty())
151 return iconPath;
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);
161 if (!readResult)
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);
166 return;
169 const QByteArray &configData = readResult.value();
170 if (configData.isEmpty())
171 return;
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}
195 loadColors();
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)
203 ; color.isValid())
205 return color;
209 if (const QColor color = m_colors.value(colorId)
210 ; color.isValid())
212 return color;
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())
228 return iconPath;
232 if (const Path iconPath = findIcon(iconId, (themeRootPath() / iconsPath))
233 ; !iconPath.isEmpty())
235 return iconPath;
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);
244 if (!readResult)
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);
249 return {};
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);
268 if (!readResult)
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);
273 return;
276 const QByteArray &configData = readResult.value();
277 if (configData.isEmpty())
278 return;
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))