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