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>
40 #include "base/global.h"
41 #include "base/logger.h"
42 #include "base/path.h"
43 #include "base/preferences.h"
44 #include "uithemecommon.h"
50 switch (qApp
->styleHints()->colorScheme())
52 case Qt::ColorScheme::Dark
:
54 case Qt::ColorScheme::Light
:
57 // fallback to custom method
58 return (qApp
->palette().color(QPalette::Active
, QPalette::Base
).lightness() < 127);
63 UIThemeManager
*UIThemeManager::m_instance
= nullptr;
65 void UIThemeManager::freeInstance()
71 void UIThemeManager::initInstance()
74 m_instance
= new UIThemeManager
;
77 UIThemeManager::UIThemeManager()
78 : m_useCustomTheme
{Preferences::instance()->useCustomUITheme()}
79 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
80 , m_useSystemIcons
{Preferences::instance()->useSystemIcons()}
83 // NOTE: Qt::QueuedConnection can be omitted as soon as support for Qt 6.5 is dropped
84 connect(QApplication::styleHints(), &QStyleHints::colorSchemeChanged
, this, &UIThemeManager::onColorSchemeChanged
, Qt::QueuedConnection
);
88 const Path themePath
= Preferences::instance()->customUIThemePath();
90 if (themePath
.hasExtension(u
".qbtheme"_s
))
92 if (QResource::registerResource(themePath
.data(), u
"/uitheme"_s
))
93 m_themeSource
= std::make_unique
<QRCThemeSource
>();
95 LogMsg(tr("Failed to load UI theme from file: \"%1\"").arg(themePath
.toString()), Log::WARNING
);
97 else if (themePath
.filename() == CONFIG_FILE_NAME
)
99 m_themeSource
= std::make_unique
<FolderThemeSource
>(themePath
.parentPath());
104 m_themeSource
= std::make_unique
<DefaultThemeSource
>();
106 if (m_useCustomTheme
)
113 UIThemeManager
*UIThemeManager::instance()
118 void UIThemeManager::applyStyleSheet() const
120 qApp
->setStyleSheet(QString::fromUtf8(m_themeSource
->readStyleSheet()));
123 void UIThemeManager::onColorSchemeChanged()
127 // workaround to refresh styled controls once color scheme is changed
128 QApplication::setStyle(QApplication::style()->name());
131 QIcon
UIThemeManager::getIcon(const QString
&iconId
, [[maybe_unused
]] const QString
&fallback
) const
133 const auto colorMode
= isDarkTheme() ? ColorMode::Dark
: ColorMode::Light
;
134 auto &icons
= (colorMode
== ColorMode::Dark
) ? m_darkModeIcons
: m_icons
;
136 const auto iter
= icons
.find(iconId
);
137 if (iter
!= icons
.end())
140 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
141 // Don't cache system icons because users might change them at run time
142 if (m_useSystemIcons
)
144 auto icon
= QIcon::fromTheme(iconId
);
145 if (icon
.isNull() || icon
.availableSizes().isEmpty())
146 icon
= QIcon::fromTheme(fallback
, QIcon(m_themeSource
->getIconPath(iconId
, colorMode
).data()));
151 const QIcon icon
{m_themeSource
->getIconPath(iconId
, colorMode
).data()};
152 icons
[iconId
] = icon
;
156 QIcon
UIThemeManager::getFlagIcon(const QString
&countryIsoCode
) const
158 if (countryIsoCode
.isEmpty())
161 const QString key
= countryIsoCode
.toLower();
162 const auto iter
= m_flags
.find(key
);
163 if (iter
!= m_flags
.end())
166 const QIcon icon
{u
":/icons/flags/" + key
+ u
".svg"};
171 QPixmap
UIThemeManager::getScaledPixmap(const QString
&iconId
, const int height
) const
173 // (workaround) svg images require the use of `QIcon()` to load and scale losslessly,
174 // otherwise other image classes will convert it to pixmap first and follow-up scaling will become lossy.
176 Q_ASSERT(height
> 0);
178 const QString cacheKey
= iconId
+ u
'@' + QString::number(height
);
181 if (!QPixmapCache::find(cacheKey
, &pixmap
))
183 pixmap
= getIcon(iconId
).pixmap(height
);
184 QPixmapCache::insert(cacheKey
, pixmap
);
190 QColor
UIThemeManager::getColor(const QString
&id
) const
192 const QColor color
= m_themeSource
->getColor(id
, (isDarkTheme() ? ColorMode::Dark
: ColorMode::Light
));
196 void UIThemeManager::applyPalette() const
198 struct ColorDescriptor
201 QPalette::ColorRole colorRole
;
202 QPalette::ColorGroup colorGroup
;
205 const ColorDescriptor paletteColorDescriptors
[] =
207 {u
"Palette.Window"_s
, QPalette::Window
, QPalette::Normal
},
208 {u
"Palette.WindowText"_s
, QPalette::WindowText
, QPalette::Normal
},
209 {u
"Palette.Base"_s
, QPalette::Base
, QPalette::Normal
},
210 {u
"Palette.AlternateBase"_s
, QPalette::AlternateBase
, QPalette::Normal
},
211 {u
"Palette.Text"_s
, QPalette::Text
, QPalette::Normal
},
212 {u
"Palette.ToolTipBase"_s
, QPalette::ToolTipBase
, QPalette::Normal
},
213 {u
"Palette.ToolTipText"_s
, QPalette::ToolTipText
, QPalette::Normal
},
214 {u
"Palette.BrightText"_s
, QPalette::BrightText
, QPalette::Normal
},
215 {u
"Palette.Highlight"_s
, QPalette::Highlight
, QPalette::Normal
},
216 {u
"Palette.HighlightedText"_s
, QPalette::HighlightedText
, QPalette::Normal
},
217 {u
"Palette.Button"_s
, QPalette::Button
, QPalette::Normal
},
218 {u
"Palette.ButtonText"_s
, QPalette::ButtonText
, QPalette::Normal
},
219 {u
"Palette.Link"_s
, QPalette::Link
, QPalette::Normal
},
220 {u
"Palette.LinkVisited"_s
, QPalette::LinkVisited
, QPalette::Normal
},
221 {u
"Palette.Light"_s
, QPalette::Light
, QPalette::Normal
},
222 {u
"Palette.Midlight"_s
, QPalette::Midlight
, QPalette::Normal
},
223 {u
"Palette.Mid"_s
, QPalette::Mid
, QPalette::Normal
},
224 {u
"Palette.Dark"_s
, QPalette::Dark
, QPalette::Normal
},
225 {u
"Palette.Shadow"_s
, QPalette::Shadow
, QPalette::Normal
},
226 {u
"Palette.WindowTextDisabled"_s
, QPalette::WindowText
, QPalette::Disabled
},
227 {u
"Palette.TextDisabled"_s
, QPalette::Text
, QPalette::Disabled
},
228 {u
"Palette.ToolTipTextDisabled"_s
, QPalette::ToolTipText
, QPalette::Disabled
},
229 {u
"Palette.BrightTextDisabled"_s
, QPalette::BrightText
, QPalette::Disabled
},
230 {u
"Palette.HighlightedTextDisabled"_s
, QPalette::HighlightedText
, QPalette::Disabled
},
231 {u
"Palette.ButtonTextDisabled"_s
, QPalette::ButtonText
, QPalette::Disabled
}
234 QPalette palette
= qApp
->palette();
235 for (const ColorDescriptor
&colorDescriptor
: paletteColorDescriptors
)
237 // For backward compatibility, the palette color overrides are read from the section of the "light mode" colors
238 const QColor newColor
= m_themeSource
->getColor(colorDescriptor
.id
, ColorMode::Light
);
239 if (newColor
.isValid())
240 palette
.setColor(colorDescriptor
.colorGroup
, colorDescriptor
.colorRole
, newColor
);
243 qApp
->setPalette(palette
);