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,
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>
35 #include <QJsonDocument>
36 #include <QJsonObject>
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"
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");
65 const Path pathPng
= dir
/ Path(iconId
+ u
".png");
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())
83 return file
.readAll();
86 class QRCThemeSource final
: public UIThemeSource
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
);
105 const Path m_qrcThemeDir
{u
":/uitheme"_qs
};
106 const Path m_qrcIconsDir
= m_qrcThemeDir
/ THEME_ICONS_DIR
;
109 class FolderThemeSource final
: public UIThemeSource
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
);
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
>();
155 UIThemeManager
*UIThemeManager::m_instance
= nullptr;
157 void UIThemeManager::freeInstance()
160 m_instance
= nullptr;
163 void UIThemeManager::initInstance()
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
);
178 LogMsg(tr("Failed to load UI theme from file: \"%1\"").arg(themePath
.toString()), Log::WARNING
);
182 loadColorsFromJSONConfig();
189 UIThemeManager
*UIThemeManager::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())
206 const QIcon icon
{getIconPathFromResources(iconId
, fallback
).data()};
207 m_iconCache
[iconId
] = 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())
220 const QIcon icon
{u
":/icons/flags/" + key
+ u
".svg"};
221 m_flagCache
[key
] = icon
;
225 QColor
UIThemeManager::getColor(const QString
&id
, const QColor
&defaultColor
) const
227 return m_colors
.value(id
, defaultColor
);
231 QIcon
UIThemeManager::getSystrayIcon() const
233 const TrayIcon::Style style
= Preferences::instance()->trayIconStyle();
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
);
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
);
255 // As a failsafe in case the enum is invalid
256 return getIcon(u
"qbittorrent-tray"_qs
);
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())
273 if (!fallback
.isEmpty())
275 const Path fallbackIcon
= m_themeSource
->iconPath(fallback
);
276 if (!fallbackIcon
.isEmpty())
281 return findIcon(iconId
, DEFAULT_ICONS_DIR
);
284 void UIThemeManager::loadColorsFromJSONConfig()
286 const QByteArray config
= m_themeSource
->readConfig();
287 if (config
.isEmpty())
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
);
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
);
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
);
312 m_colors
.insert(color
.key(), providedColor
);
316 void UIThemeManager::applyPalette() const
318 struct ColorDescriptor
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
);