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"