Use tray icon from system theme only if option is set
[qBittorrent.git] / src / gui / uithemesource.cpp
blob04b41d3d91956d2c0f46ffe0c2b9c1cb21568168
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 <QFile>
34 #include <QJsonDocument>
35 #include <QJsonObject>
37 #include "base/global.h"
38 #include "base/logger.h"
39 #include "base/profile.h"
41 namespace
43 QByteArray readFile(const Path &filePath)
45 QFile file {filePath.data()};
46 if (!file.exists())
47 return {};
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())
54 , Log::WARNING);
55 return {};
58 QJsonObject parseThemeConfig(const QByteArray &data)
60 if (data.isEmpty())
61 return {};
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);
69 return {};
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);
76 return {};
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()};
88 if (!color.isValid())
90 LogMsg(UIThemeSource::tr("Invalid color for ID \"%1\" is provided by theme")
91 .arg(colorNode.key()), Log::WARNING);
92 continue;
95 colors.insert(colorNode.key(), color);
98 return colors;
101 Path findIcon(const QString &iconId, const Path &dir)
103 const Path pathSvg = dir / Path(iconId + u".svg");
104 if (pathSvg.exists())
105 return pathSvg;
107 const Path pathPng = dir / Path(iconId + u".png");
108 if (pathPng.exists())
109 return pathPng;
111 return {};
115 DefaultThemeSource::DefaultThemeSource()
116 : m_defaultPath {u":"_qs}
117 , m_userPath {specialFolderLocation(SpecialFolder::Config) / Path(u"themes/default"_qs)}
118 , m_colors {defaultUIThemeColors()}
120 loadColors();
123 QByteArray DefaultThemeSource::readStyleSheet()
125 return {};
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())
145 return iconPath;
148 if (const Path iconPath = findIcon(iconId, (m_defaultPath / darkModeIconsPath))
149 ; !iconPath.isEmpty())
151 return iconPath;
154 else
156 if (const Path iconPath = findIcon(iconId, (m_userPath / lightModeIconsPath))
157 ; !iconPath.isEmpty())
159 return iconPath;
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())
170 return;
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)
196 ; color.isValid())
198 return color;
202 if (const QColor color = m_colors.value(colorId)
203 ; color.isValid())
205 return color;
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())
221 return iconPath;
225 if (const Path iconPath = findIcon(iconId, (themeRootPath() / iconsPath))
226 ; !iconPath.isEmpty())
228 return iconPath;
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())
248 return;
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
279 return m_folder;