Merge pull request #18104 from sledgehammer999/remove_dead_code
[qBittorrent.git] / src / gui / uithememanager.cpp
blob54d9f66237f2fb6827df13d382021be2a82b7588
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2019, 2021 Prince Gupta <jagannatharjun11@gmail.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 * USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If
25 * you modify file(s), you may extend this exception to your version of the
26 * file(s), but you are not obligated to do so. If you do not wish to do so,
27 * delete this exception statement from your version.
30 #include "uithememanager.h"
32 #include <QApplication>
33 #include <QDir>
34 #include <QFile>
35 #include <QJsonDocument>
36 #include <QJsonObject>
37 #include <QPalette>
38 #include <QResource>
40 #include "base/global.h"
41 #include "base/logger.h"
42 #include "base/path.h"
43 #include "base/preferences.h"
44 #include "base/utils/fs.h"
46 namespace
48 const Path DEFAULT_ICONS_DIR {u":icons"_qs};
49 const QString CONFIG_FILE_NAME = u"config.json"_qs;
50 const QString STYLESHEET_FILE_NAME = u"stylesheet.qss"_qs;
52 // Directory used by stylesheet to reference internal resources
53 // for example `icon: url(:/uitheme/file.svg)` will be expected to
54 // point to a file `file.svg` in root directory of CONFIG_FILE_NAME
55 const QString STYLESHEET_RESOURCES_DIR = u":/uitheme"_qs;
57 const Path THEME_ICONS_DIR {u"icons"_qs};
59 Path findIcon(const QString &iconId, const Path &dir)
61 const Path pathSvg = dir / Path(iconId + u".svg");
62 if (pathSvg.exists())
63 return pathSvg;
65 const Path pathPng = dir / Path(iconId + u".png");
66 if (pathPng.exists())
67 return pathPng;
69 return {};
72 QByteArray readFile(const Path &filePath)
74 QFile file {filePath.data()};
75 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
77 LogMsg(UIThemeManager::tr("UITheme - Failed to open \"%1\". Reason: %2")
78 .arg(filePath.filename(), file.errorString())
79 , Log::WARNING);
80 return {};
83 return file.readAll();
86 class QRCThemeSource final : public UIThemeSource
88 public:
89 QByteArray readStyleSheet() override
91 return readFile(m_qrcThemeDir / Path(STYLESHEET_FILE_NAME));
94 QByteArray readConfig() override
96 return readFile(m_qrcThemeDir / Path(CONFIG_FILE_NAME));
99 Path iconPath(const QString &iconId) const override
101 return findIcon(iconId, m_qrcIconsDir);
104 private:
105 const Path m_qrcThemeDir {u":/uitheme"_qs};
106 const Path m_qrcIconsDir = m_qrcThemeDir / THEME_ICONS_DIR;
109 class FolderThemeSource final : public UIThemeSource
111 public:
112 explicit FolderThemeSource(const Path &folderPath)
113 : m_folder {folderPath}
114 , m_iconsDir {m_folder / THEME_ICONS_DIR}
118 QByteArray readStyleSheet() override
120 QByteArray styleSheetData = readFile(m_folder / Path(STYLESHEET_FILE_NAME));
121 return styleSheetData.replace(STYLESHEET_RESOURCES_DIR.toUtf8(), m_folder.data().toUtf8());
124 QByteArray readConfig() override
126 return readFile(m_folder / Path(CONFIG_FILE_NAME));
129 Path iconPath(const QString &iconId) const override
131 return findIcon(iconId, m_iconsDir);
134 private:
135 const Path m_folder;
136 const Path m_iconsDir;
140 std::unique_ptr<UIThemeSource> createUIThemeSource(const Path &themePath)
142 if (themePath.filename() == CONFIG_FILE_NAME)
143 return std::make_unique<FolderThemeSource>(themePath);
145 if ((themePath.hasExtension(u".qbtheme"_qs))
146 && QResource::registerResource(themePath.data(), u"/uitheme"_qs))
148 return std::make_unique<QRCThemeSource>();
151 return nullptr;
155 UIThemeManager *UIThemeManager::m_instance = nullptr;
157 void UIThemeManager::freeInstance()
159 delete m_instance;
160 m_instance = nullptr;
163 void UIThemeManager::initInstance()
165 if (!m_instance)
166 m_instance = new UIThemeManager;
169 UIThemeManager::UIThemeManager()
170 : m_useCustomTheme(Preferences::instance()->useCustomUITheme())
172 if (m_useCustomTheme)
174 const Path themePath = Preferences::instance()->customUIThemePath();
175 m_themeSource = createUIThemeSource(themePath);
176 if (!m_themeSource)
178 LogMsg(tr("Failed to load UI theme from file: \"%1\"").arg(themePath.toString()), Log::WARNING);
180 else
182 loadColorsFromJSONConfig();
183 applyPalette();
184 applyStyleSheet();
189 UIThemeManager *UIThemeManager::instance()
191 return m_instance;
194 void UIThemeManager::applyStyleSheet() const
196 qApp->setStyleSheet(QString::fromUtf8(m_themeSource->readStyleSheet()));
199 QIcon UIThemeManager::getIcon(const QString &iconId, const QString &fallback) const
201 // Cache to avoid rescaling svg icons
202 const auto iter = m_iconCache.find(iconId);
203 if (iter != m_iconCache.end())
204 return *iter;
206 const QIcon icon {getIconPathFromResources(iconId, fallback).data()};
207 m_iconCache[iconId] = icon;
208 return icon;
211 QIcon UIThemeManager::getFlagIcon(const QString &countryIsoCode) const
213 if (countryIsoCode.isEmpty()) return {};
215 const QString key = countryIsoCode.toLower();
216 const auto iter = m_flagCache.find(key);
217 if (iter != m_flagCache.end())
218 return *iter;
220 const QIcon icon {u":/icons/flags/" + key + u".svg"};
221 m_flagCache[key] = icon;
222 return icon;
225 QColor UIThemeManager::getColor(const QString &id, const QColor &defaultColor) const
227 return m_colors.value(id, defaultColor);
230 #ifndef Q_OS_MACOS
231 QIcon UIThemeManager::getSystrayIcon() const
233 const TrayIcon::Style style = Preferences::instance()->trayIconStyle();
234 switch (style)
236 #if defined(Q_OS_UNIX)
237 case TrayIcon::Style::Normal:
238 return QIcon::fromTheme(u"qbittorrent-tray"_qs);
239 case TrayIcon::Style::MonoDark:
240 return QIcon::fromTheme(u"qbittorrent-tray-dark"_qs);
241 case TrayIcon::Style::MonoLight:
242 return QIcon::fromTheme(u"qbittorrent-tray-light"_qs);
243 #else
244 case TrayIcon::Style::Normal:
245 return getIcon(u"qbittorrent-tray"_qs);
246 case TrayIcon::Style::MonoDark:
247 return getIcon(u"qbittorrent-tray-dark"_qs);
248 case TrayIcon::Style::MonoLight:
249 return getIcon(u"qbittorrent-tray-light"_qs);
250 #endif
251 default:
252 break;
255 // As a failsafe in case the enum is invalid
256 return getIcon(u"qbittorrent-tray"_qs);
258 #endif
260 Path UIThemeManager::getIconPath(const QString &iconId) const
262 return getIconPathFromResources(iconId, {});
265 Path UIThemeManager::getIconPathFromResources(const QString &iconId, const QString &fallback) const
267 if (m_useCustomTheme && m_themeSource)
269 const Path customIcon = m_themeSource->iconPath(iconId);
270 if (!customIcon.isEmpty())
271 return customIcon;
273 if (!fallback.isEmpty())
275 const Path fallbackIcon = m_themeSource->iconPath(fallback);
276 if (!fallbackIcon.isEmpty())
277 return fallbackIcon;
281 return findIcon(iconId, DEFAULT_ICONS_DIR);
284 void UIThemeManager::loadColorsFromJSONConfig()
286 const QByteArray config = m_themeSource->readConfig();
287 if (config.isEmpty())
288 return;
290 QJsonParseError jsonError;
291 const QJsonDocument configJsonDoc = QJsonDocument::fromJson(config, &jsonError);
292 if (jsonError.error != QJsonParseError::NoError)
294 LogMsg(tr("\"%1\" has invalid format. Reason: %2").arg(CONFIG_FILE_NAME, jsonError.errorString()), Log::WARNING);
295 return;
297 if (!configJsonDoc.isObject())
299 LogMsg(tr("\"%1\" has invalid format. Reason: %2").arg(CONFIG_FILE_NAME, tr("Root JSON value is not an object")), Log::WARNING);
300 return;
303 const QJsonObject colors = configJsonDoc.object().value(u"colors").toObject();
304 for (auto color = colors.constBegin(); color != colors.constEnd(); ++color)
306 const QColor providedColor(color.value().toString());
307 if (!providedColor.isValid())
309 LogMsg(tr("Invalid color for ID \"%1\" is provided by theme").arg(color.key()), Log::WARNING);
310 continue;
312 m_colors.insert(color.key(), providedColor);
316 void UIThemeManager::applyPalette() const
318 struct ColorDescriptor
320 QString id;
321 QPalette::ColorRole colorRole;
322 QPalette::ColorGroup colorGroup;
325 const ColorDescriptor paletteColorDescriptors[] =
327 {u"Palette.Window"_qs, QPalette::Window, QPalette::Normal},
328 {u"Palette.WindowText"_qs, QPalette::WindowText, QPalette::Normal},
329 {u"Palette.Base"_qs, QPalette::Base, QPalette::Normal},
330 {u"Palette.AlternateBase"_qs, QPalette::AlternateBase, QPalette::Normal},
331 {u"Palette.Text"_qs, QPalette::Text, QPalette::Normal},
332 {u"Palette.ToolTipBase"_qs, QPalette::ToolTipBase, QPalette::Normal},
333 {u"Palette.ToolTipText"_qs, QPalette::ToolTipText, QPalette::Normal},
334 {u"Palette.BrightText"_qs, QPalette::BrightText, QPalette::Normal},
335 {u"Palette.Highlight"_qs, QPalette::Highlight, QPalette::Normal},
336 {u"Palette.HighlightedText"_qs, QPalette::HighlightedText, QPalette::Normal},
337 {u"Palette.Button"_qs, QPalette::Button, QPalette::Normal},
338 {u"Palette.ButtonText"_qs, QPalette::ButtonText, QPalette::Normal},
339 {u"Palette.Link"_qs, QPalette::Link, QPalette::Normal},
340 {u"Palette.LinkVisited"_qs, QPalette::LinkVisited, QPalette::Normal},
341 {u"Palette.Light"_qs, QPalette::Light, QPalette::Normal},
342 {u"Palette.Midlight"_qs, QPalette::Midlight, QPalette::Normal},
343 {u"Palette.Mid"_qs, QPalette::Mid, QPalette::Normal},
344 {u"Palette.Dark"_qs, QPalette::Dark, QPalette::Normal},
345 {u"Palette.Shadow"_qs, QPalette::Shadow, QPalette::Normal},
346 {u"Palette.WindowTextDisabled"_qs, QPalette::WindowText, QPalette::Disabled},
347 {u"Palette.TextDisabled"_qs, QPalette::Text, QPalette::Disabled},
348 {u"Palette.ToolTipTextDisabled"_qs, QPalette::ToolTipText, QPalette::Disabled},
349 {u"Palette.BrightTextDisabled"_qs, QPalette::BrightText, QPalette::Disabled},
350 {u"Palette.HighlightedTextDisabled"_qs, QPalette::HighlightedText, QPalette::Disabled},
351 {u"Palette.ButtonTextDisabled"_qs, QPalette::ButtonText, QPalette::Disabled}
354 QPalette palette = qApp->palette();
355 for (const ColorDescriptor &colorDescriptor : paletteColorDescriptors)
357 const QColor defaultColor = palette.color(colorDescriptor.colorGroup, colorDescriptor.colorRole);
358 const QColor newColor = getColor(colorDescriptor.id, defaultColor);
359 palette.setColor(colorDescriptor.colorGroup, colorDescriptor.colorRole, newColor);
361 qApp->setPalette(palette);