2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2023-2024 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,
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 "uithememanager.h"
33 #include <QApplication>
35 #include <QPixmapCache>
38 #include <QStyleHints>
41 #include <QOperatingSystemVersion>
44 #include "base/global.h"
45 #include "base/logger.h"
46 #include "base/path.h"
47 #include "base/preferences.h"
48 #include "uithemecommon.h"
54 switch (qApp
->styleHints()->colorScheme())
56 case Qt::ColorScheme::Dark
:
58 case Qt::ColorScheme::Light
:
61 // fallback to custom method
62 return (qApp
->palette().color(QPalette::Active
, QPalette::Base
).lightness() < 127);
67 UIThemeManager
*UIThemeManager::m_instance
= nullptr;
69 void UIThemeManager::freeInstance()
75 void UIThemeManager::initInstance()
78 m_instance
= new UIThemeManager
;
81 UIThemeManager::UIThemeManager()
82 : m_useCustomTheme
{Preferences::instance()->useCustomUITheme()}
83 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
84 , m_useSystemIcons
{Preferences::instance()->useSystemIcons()}
88 const QString defaultStyle
= (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10
) ? u
"Fusion"_s
: QString();
89 if (const QString styleName
= Preferences::instance()->getStyle(); !QApplication::setStyle(styleName
.isEmpty() ? defaultStyle
: styleName
))
90 LogMsg(tr("Set app style failed. Unknown style: \"%1\"").arg(styleName
), Log::WARNING
);
93 // NOTE: Qt::QueuedConnection can be omitted as soon as support for Qt 6.5 is dropped
94 connect(QApplication::styleHints(), &QStyleHints::colorSchemeChanged
, this, &UIThemeManager::onColorSchemeChanged
, Qt::QueuedConnection
);
98 const Path themePath
= Preferences::instance()->customUIThemePath();
100 if (themePath
.hasExtension(u
".qbtheme"_s
))
102 if (QResource::registerResource(themePath
.data(), u
"/uitheme"_s
))
103 m_themeSource
= std::make_unique
<QRCThemeSource
>();
105 LogMsg(tr("Failed to load UI theme from file: \"%1\"").arg(themePath
.toString()), Log::WARNING
);
107 else if (themePath
.filename() == CONFIG_FILE_NAME
)
109 m_themeSource
= std::make_unique
<FolderThemeSource
>(themePath
.parentPath());
114 m_themeSource
= std::make_unique
<DefaultThemeSource
>();
116 if (m_useCustomTheme
)
123 UIThemeManager
*UIThemeManager::instance()
128 void UIThemeManager::applyStyleSheet() const
130 qApp
->setStyleSheet(QString::fromUtf8(m_themeSource
->readStyleSheet()));
133 void UIThemeManager::onColorSchemeChanged()
137 // workaround to refresh styled controls once color scheme is changed
138 QApplication::setStyle(QApplication::style()->name());
141 QIcon
UIThemeManager::getIcon(const QString
&iconId
, [[maybe_unused
]] const QString
&fallback
) const
143 const auto colorMode
= isDarkTheme() ? ColorMode::Dark
: ColorMode::Light
;
144 auto &icons
= (colorMode
== ColorMode::Dark
) ? m_darkModeIcons
: m_icons
;
146 const auto iter
= icons
.find(iconId
);
147 if (iter
!= icons
.end())
150 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
151 // Don't cache system icons because users might change them at run time
152 if (m_useSystemIcons
)
154 auto icon
= QIcon::fromTheme(iconId
);
155 if (icon
.isNull() || icon
.availableSizes().isEmpty())
156 icon
= QIcon::fromTheme(fallback
, QIcon(m_themeSource
->getIconPath(iconId
, colorMode
).data()));
161 const QIcon icon
{m_themeSource
->getIconPath(iconId
, colorMode
).data()};
162 icons
[iconId
] = icon
;
166 QIcon
UIThemeManager::getFlagIcon(const QString
&countryIsoCode
) const
168 if (countryIsoCode
.isEmpty())
171 const QString key
= countryIsoCode
.toLower();
172 const auto iter
= m_flags
.find(key
);
173 if (iter
!= m_flags
.end())
176 const QIcon icon
{u
":/icons/flags/" + key
+ u
".svg"};
181 QPixmap
UIThemeManager::getScaledPixmap(const QString
&iconId
, const int height
) const
183 // (workaround) svg images require the use of `QIcon()` to load and scale losslessly,
184 // otherwise other image classes will convert it to pixmap first and follow-up scaling will become lossy.
186 Q_ASSERT(height
> 0);
188 const QString cacheKey
= iconId
+ u
'@' + QString::number(height
);
191 if (!QPixmapCache::find(cacheKey
, &pixmap
))
193 pixmap
= getIcon(iconId
).pixmap(height
);
194 QPixmapCache::insert(cacheKey
, pixmap
);
200 QColor
UIThemeManager::getColor(const QString
&id
) const
202 const QColor color
= m_themeSource
->getColor(id
, (isDarkTheme() ? ColorMode::Dark
: ColorMode::Light
));
206 void UIThemeManager::applyPalette() const
208 struct ColorDescriptor
211 QPalette::ColorRole colorRole
;
212 QPalette::ColorGroup colorGroup
;
215 const ColorDescriptor paletteColorDescriptors
[] =
217 {u
"Palette.Window"_s
, QPalette::Window
, QPalette::Normal
},
218 {u
"Palette.WindowText"_s
, QPalette::WindowText
, QPalette::Normal
},
219 {u
"Palette.Base"_s
, QPalette::Base
, QPalette::Normal
},
220 {u
"Palette.AlternateBase"_s
, QPalette::AlternateBase
, QPalette::Normal
},
221 {u
"Palette.Text"_s
, QPalette::Text
, QPalette::Normal
},
222 {u
"Palette.ToolTipBase"_s
, QPalette::ToolTipBase
, QPalette::Normal
},
223 {u
"Palette.ToolTipText"_s
, QPalette::ToolTipText
, QPalette::Normal
},
224 {u
"Palette.BrightText"_s
, QPalette::BrightText
, QPalette::Normal
},
225 {u
"Palette.Highlight"_s
, QPalette::Highlight
, QPalette::Normal
},
226 {u
"Palette.HighlightedText"_s
, QPalette::HighlightedText
, QPalette::Normal
},
227 {u
"Palette.Button"_s
, QPalette::Button
, QPalette::Normal
},
228 {u
"Palette.ButtonText"_s
, QPalette::ButtonText
, QPalette::Normal
},
229 {u
"Palette.Link"_s
, QPalette::Link
, QPalette::Normal
},
230 {u
"Palette.LinkVisited"_s
, QPalette::LinkVisited
, QPalette::Normal
},
231 {u
"Palette.Light"_s
, QPalette::Light
, QPalette::Normal
},
232 {u
"Palette.Midlight"_s
, QPalette::Midlight
, QPalette::Normal
},
233 {u
"Palette.Mid"_s
, QPalette::Mid
, QPalette::Normal
},
234 {u
"Palette.Dark"_s
, QPalette::Dark
, QPalette::Normal
},
235 {u
"Palette.Shadow"_s
, QPalette::Shadow
, QPalette::Normal
},
236 {u
"Palette.WindowTextDisabled"_s
, QPalette::WindowText
, QPalette::Disabled
},
237 {u
"Palette.TextDisabled"_s
, QPalette::Text
, QPalette::Disabled
},
238 {u
"Palette.ToolTipTextDisabled"_s
, QPalette::ToolTipText
, QPalette::Disabled
},
239 {u
"Palette.BrightTextDisabled"_s
, QPalette::BrightText
, QPalette::Disabled
},
240 {u
"Palette.HighlightedTextDisabled"_s
, QPalette::HighlightedText
, QPalette::Disabled
},
241 {u
"Palette.ButtonTextDisabled"_s
, QPalette::ButtonText
, QPalette::Disabled
}
244 QPalette palette
= qApp
->palette();
245 for (const ColorDescriptor
&colorDescriptor
: paletteColorDescriptors
)
247 // For backward compatibility, the palette color overrides are read from the section of the "light mode" colors
248 const QColor newColor
= m_themeSource
->getColor(colorDescriptor
.id
, ColorMode::Light
);
249 if (newColor
.isValid())
250 palette
.setColor(colorDescriptor
.colorGroup
, colorDescriptor
.colorRole
, newColor
);
253 qApp
->setPalette(palette
);