File indexing completed on 2023-12-03 04:13:26
0001 /* 0002 SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "plasmadesktoptheme.h" 0008 #include <KIconLoader> 0009 #include <QDebug> 0010 #include <QGuiApplication> 0011 #include <QPalette> 0012 #include <QQmlContext> 0013 #include <QQmlEngine> 0014 #include <QQuickRenderControl> 0015 #include <QQuickWindow> 0016 #include <QScopeGuard> 0017 0018 #include <KColorScheme> 0019 #include <KConfigGroup> 0020 #include <KIconColors> 0021 #include <QDBusConnection> 0022 0023 class StyleSingleton : public QObject 0024 { 0025 Q_OBJECT 0026 0027 public: 0028 struct Colors { 0029 QPalette palette; 0030 KColorScheme selectionScheme; 0031 KColorScheme scheme; 0032 }; 0033 0034 explicit StyleSingleton() 0035 : QObject() 0036 , buttonScheme(QPalette::Active, KColorScheme::ColorSet::Button) 0037 , viewScheme(QPalette::Active, KColorScheme::ColorSet::View) 0038 { 0039 connect(qGuiApp, &QGuiApplication::paletteChanged, this, &StyleSingleton::refresh); 0040 0041 // Use DBus in order to listen for settings changes directly, as the 0042 // QApplication doesn't expose the font variants we're looking for, 0043 // namely smallFont. 0044 QDBusConnection::sessionBus().connect(QString(), 0045 QStringLiteral("/KDEPlatformTheme"), 0046 QStringLiteral("org.kde.KDEPlatformTheme"), 0047 QStringLiteral("refreshFonts"), 0048 this, 0049 SLOT(notifyWatchersConfigurationChange())); 0050 0051 connect(qGuiApp, &QGuiApplication::fontDatabaseChanged, this, &StyleSingleton::notifyWatchersConfigurationChange); 0052 connect(qGuiApp, &QGuiApplication::fontChanged, this, &StyleSingleton::notifyWatchersConfigurationChange); 0053 0054 // Use NativeTextRendering as the default text rendering type when the scale factor is an integer. 0055 // NativeTextRendering is still distorted sometimes with fractional scale factors, 0056 // despite https://bugreports.qt.io/browse/QTBUG-67007 being closed. 0057 qreal devicePixelRatio = qGuiApp->devicePixelRatio(); 0058 QQuickWindow::TextRenderType defaultTextRenderType = 0059 int(devicePixelRatio) == devicePixelRatio ? QQuickWindow::NativeTextRendering : QQuickWindow::QtTextRendering; 0060 QQuickWindow::setTextRenderType(defaultTextRenderType); 0061 0062 smallFont = loadSmallFont(); 0063 } 0064 0065 QFont loadSmallFont() const 0066 { 0067 KSharedConfigPtr ptr = KSharedConfig::openConfig(); 0068 KConfigGroup general(ptr->group("general")); 0069 0070 return general.readEntry("smallestReadableFont", []() { 0071 auto smallFont = qApp->font(); 0072 #ifndef Q_OS_WIN 0073 if (smallFont.pixelSize() != -1) { 0074 smallFont.setPixelSize(smallFont.pixelSize() - 2); 0075 } else { 0076 smallFont.setPointSize(smallFont.pointSize() - 2); 0077 } 0078 #endif 0079 return smallFont; 0080 }()); 0081 } 0082 0083 void refresh() 0084 { 0085 m_cache.clear(); 0086 buttonScheme = KColorScheme(QPalette::Active, KColorScheme::ColorSet::Button); 0087 viewScheme = KColorScheme(QPalette::Active, KColorScheme::ColorSet::View); 0088 0089 notifyWatchersPaletteChange(); 0090 } 0091 0092 Colors loadColors(Kirigami::PlatformTheme::ColorSet cs, QPalette::ColorGroup group) 0093 { 0094 const auto key = qMakePair(cs, group); 0095 auto it = m_cache.constFind(key); 0096 if (it != m_cache.constEnd()) { 0097 return *it; 0098 } 0099 0100 using Kirigami::PlatformTheme; 0101 0102 KColorScheme::ColorSet set; 0103 0104 switch (cs) { 0105 case PlatformTheme::Button: 0106 set = KColorScheme::ColorSet::Button; 0107 break; 0108 case PlatformTheme::Selection: 0109 set = KColorScheme::ColorSet::Selection; 0110 break; 0111 case PlatformTheme::Tooltip: 0112 set = KColorScheme::ColorSet::Tooltip; 0113 break; 0114 case PlatformTheme::View: 0115 set = KColorScheme::ColorSet::View; 0116 break; 0117 case PlatformTheme::Complementary: 0118 set = KColorScheme::ColorSet::Complementary; 0119 break; 0120 case PlatformTheme::Header: 0121 set = KColorScheme::ColorSet::Header; 0122 break; 0123 case PlatformTheme::Window: 0124 default: 0125 set = KColorScheme::ColorSet::Window; 0126 } 0127 0128 Colors ret = {{}, KColorScheme(group, KColorScheme::ColorSet::Selection), KColorScheme(group, set)}; 0129 0130 QPalette pal; 0131 for (auto state : {QPalette::Active, QPalette::Inactive, QPalette::Disabled}) { 0132 pal.setBrush(state, QPalette::WindowText, ret.scheme.foreground()); 0133 pal.setBrush(state, QPalette::Window, ret.scheme.background()); 0134 pal.setBrush(state, QPalette::Base, ret.scheme.background()); 0135 pal.setBrush(state, QPalette::Text, ret.scheme.foreground()); 0136 pal.setBrush(state, QPalette::Button, ret.scheme.background()); 0137 pal.setBrush(state, QPalette::ButtonText, ret.scheme.foreground()); 0138 pal.setBrush(state, QPalette::Highlight, ret.selectionScheme.background()); 0139 pal.setBrush(state, QPalette::HighlightedText, ret.selectionScheme.foreground()); 0140 pal.setBrush(state, QPalette::ToolTipBase, ret.scheme.background()); 0141 pal.setBrush(state, QPalette::ToolTipText, ret.scheme.foreground()); 0142 0143 pal.setColor(state, QPalette::Light, ret.scheme.shade(KColorScheme::LightShade)); 0144 pal.setColor(state, QPalette::Midlight, ret.scheme.shade(KColorScheme::MidlightShade)); 0145 pal.setColor(state, QPalette::Mid, ret.scheme.shade(KColorScheme::MidShade)); 0146 pal.setColor(state, QPalette::Dark, ret.scheme.shade(KColorScheme::DarkShade)); 0147 pal.setColor(state, QPalette::Shadow, ret.scheme.shade(KColorScheme::ShadowShade)); 0148 0149 pal.setBrush(state, QPalette::AlternateBase, ret.scheme.background(KColorScheme::AlternateBackground)); 0150 pal.setBrush(state, QPalette::Link, ret.scheme.foreground(KColorScheme::LinkText)); 0151 pal.setBrush(state, QPalette::LinkVisited, ret.scheme.foreground(KColorScheme::VisitedText)); 0152 } 0153 ret.palette = pal; 0154 m_cache.insert(key, ret); 0155 return ret; 0156 } 0157 0158 void notifyWatchersPaletteChange() 0159 { 0160 for (auto watcher : std::as_const(watchers)) { 0161 watcher->syncColors(); 0162 } 0163 } 0164 0165 Q_SLOT void notifyWatchersConfigurationChange() 0166 { 0167 smallFont = loadSmallFont(); 0168 for (auto watcher : std::as_const(watchers)) { 0169 watcher->setSmallFont(smallFont); 0170 watcher->setDefaultFont(qApp->font()); 0171 } 0172 } 0173 0174 KColorScheme buttonScheme; 0175 KColorScheme viewScheme; 0176 QFont smallFont; 0177 0178 QVector<PlasmaDesktopTheme *> watchers; 0179 0180 private: 0181 QHash<QPair<Kirigami::PlatformTheme::ColorSet, QPalette::ColorGroup>, Colors> m_cache; 0182 }; 0183 0184 Q_GLOBAL_STATIC(StyleSingleton, s_style); 0185 0186 PlasmaDesktopTheme::PlasmaDesktopTheme(QObject *parent) 0187 : PlatformTheme(parent) 0188 { 0189 setSupportsIconColoring(true); 0190 0191 auto parentItem = qobject_cast<QQuickItem *>(parent); 0192 if (parentItem) { 0193 connect(parentItem, &QQuickItem::enabledChanged, this, &PlasmaDesktopTheme::syncColors); 0194 connect(parentItem, &QQuickItem::visibleChanged, this, &PlasmaDesktopTheme::syncColors); 0195 connect(parentItem, &QQuickItem::windowChanged, this, &PlasmaDesktopTheme::syncWindow); 0196 } 0197 0198 s_style->watchers.append(this); 0199 0200 setDefaultFont(qGuiApp->font()); 0201 setSmallFont(s_style->smallFont); 0202 0203 syncWindow(); 0204 if (!m_window) { 0205 syncColors(); 0206 } 0207 } 0208 0209 PlasmaDesktopTheme::~PlasmaDesktopTheme() 0210 { 0211 s_style->watchers.removeOne(this); 0212 } 0213 0214 void PlasmaDesktopTheme::syncWindow() 0215 { 0216 if (m_window) { 0217 disconnect(m_window.data(), &QWindow::activeChanged, this, &PlasmaDesktopTheme::syncColors); 0218 } 0219 0220 QWindow *window = nullptr; 0221 0222 auto parentItem = qobject_cast<QQuickItem *>(parent()); 0223 if (parentItem) { 0224 QQuickWindow *qw = parentItem->window(); 0225 0226 window = QQuickRenderControl::renderWindowFor(qw); 0227 if (!window) { 0228 window = qw; 0229 } 0230 if (qw) { 0231 connect(qw, &QQuickWindow::sceneGraphInitialized, this, &PlasmaDesktopTheme::syncWindow, Qt::UniqueConnection); 0232 } 0233 } 0234 m_window = window; 0235 0236 if (window) { 0237 connect(m_window.data(), &QWindow::activeChanged, this, &PlasmaDesktopTheme::syncColors); 0238 syncColors(); 0239 } 0240 } 0241 0242 QIcon PlasmaDesktopTheme::iconFromTheme(const QString &name, const QColor &customColor) 0243 { 0244 if (customColor != Qt::transparent) { 0245 KIconColors colors; 0246 colors.setText(customColor); 0247 return KDE::icon(name, colors); 0248 } else { 0249 return KDE::icon(name); 0250 } 0251 } 0252 0253 void PlasmaDesktopTheme::syncColors() 0254 { 0255 if (QCoreApplication::closingDown()) { 0256 return; 0257 } 0258 0259 QPalette::ColorGroup group = (QPalette::ColorGroup)colorGroup(); 0260 auto parentItem = qobject_cast<QQuickItem *>(parent()); 0261 if (parentItem) { 0262 if (!parentItem->isVisible()) { 0263 return; 0264 } 0265 if (!parentItem->isEnabled()) { 0266 group = QPalette::Disabled; 0267 // Why also checking the window is exposed? 0268 // in the case of QQuickWidget the window() will never be active 0269 // and the widgets will always have the inactive palette. 0270 // better to always show it active than always show it inactive 0271 } else if (m_window && !m_window->isActive() && m_window->isExposed()) { 0272 group = QPalette::Inactive; 0273 } 0274 } 0275 0276 const auto colors = s_style->loadColors(colorSet(), group); 0277 0278 // foreground 0279 setTextColor(colors.scheme.foreground(KColorScheme::NormalText).color()); 0280 setDisabledTextColor(colors.scheme.foreground(KColorScheme::InactiveText).color()); 0281 setHighlightedTextColor(colors.selectionScheme.foreground(KColorScheme::NormalText).color()); 0282 setActiveTextColor(colors.scheme.foreground(KColorScheme::ActiveText).color()); 0283 setLinkColor(colors.scheme.foreground(KColorScheme::LinkText).color()); 0284 setVisitedLinkColor(colors.scheme.foreground(KColorScheme::VisitedText).color()); 0285 setNegativeTextColor(colors.scheme.foreground(KColorScheme::NegativeText).color()); 0286 setNeutralTextColor(colors.scheme.foreground(KColorScheme::NeutralText).color()); 0287 setPositiveTextColor(colors.scheme.foreground(KColorScheme::PositiveText).color()); 0288 0289 // background 0290 setBackgroundColor(colors.scheme.background(KColorScheme::NormalBackground).color()); 0291 setAlternateBackgroundColor(colors.scheme.background(KColorScheme::AlternateBackground).color()); 0292 setHighlightColor(colors.selectionScheme.background(KColorScheme::NormalBackground).color()); 0293 setActiveBackgroundColor(colors.scheme.background(KColorScheme::ActiveBackground).color()); 0294 setLinkBackgroundColor(colors.scheme.background(KColorScheme::LinkBackground).color()); 0295 setVisitedLinkBackgroundColor(colors.scheme.background(KColorScheme::VisitedBackground).color()); 0296 setNegativeBackgroundColor(colors.scheme.background(KColorScheme::NegativeBackground).color()); 0297 setNeutralBackgroundColor(colors.scheme.background(KColorScheme::NeutralBackground).color()); 0298 setPositiveBackgroundColor(colors.scheme.background(KColorScheme::PositiveBackground).color()); 0299 0300 // decoration 0301 setHoverColor(colors.scheme.decoration(KColorScheme::HoverColor).color()); 0302 setFocusColor(colors.scheme.decoration(KColorScheme::FocusColor).color()); 0303 } 0304 0305 bool PlasmaDesktopTheme::event(QEvent *event) 0306 { 0307 if (event->type() == Kirigami::PlatformThemeEvents::DataChangedEvent::type) { 0308 syncColors(); 0309 } 0310 0311 if (event->type() == Kirigami::PlatformThemeEvents::ColorSetChangedEvent::type) { 0312 syncColors(); 0313 } 0314 0315 if (event->type() == Kirigami::PlatformThemeEvents::ColorGroupChangedEvent::type) { 0316 syncColors(); 0317 } 0318 0319 return PlatformTheme::event(event); 0320 } 0321 0322 #include "moc_plasmadesktoptheme.cpp" 0323 #include "plasmadesktoptheme.moc"