Warning, file /plasma/qqc2-breeze-style/kirigami-plasmadesktop-integration/plasmadesktoptheme.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
0004     SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "plasmadesktoptheme.h"
0009 #include <KColorScheme>
0010 #include <KColorUtils>
0011 #include <KConfigGroup>
0012 #include <QDebug>
0013 #include <QGuiApplication>
0014 #include <QPalette>
0015 #include <QQmlContext>
0016 #include <QQmlEngine>
0017 #include <QQuickRenderControl>
0018 #include <QQuickWindow>
0019 #include <QScopeGuard>
0020 
0021 #ifndef Q_OS_ANDROID
0022 #include <QDBusConnection>
0023 
0024 #include <KIconColors>
0025 #include <KIconLoader>
0026 #endif
0027 
0028 class StyleSingleton : public QObject
0029 {
0030     Q_OBJECT
0031 
0032 public:
0033     struct Colors {
0034         QPalette palette;
0035         KColorScheme selectionScheme;
0036         KColorScheme scheme;
0037     };
0038 
0039     explicit StyleSingleton()
0040         : QObject()
0041         , buttonScheme(QPalette::Active, KColorScheme::ColorSet::Button)
0042     {
0043         connect(qGuiApp, &QGuiApplication::paletteChanged, this, &StyleSingleton::refresh);
0044 
0045 #ifndef Q_OS_ANDROID
0046         // Use DBus in order to listen for settings changes directly, as the
0047         // QApplication doesn't expose the font variants we're looking for,
0048         // namely smallFont.
0049         QDBusConnection::sessionBus().connect(QString(),
0050                                               QStringLiteral("/KDEPlatformTheme"),
0051                                               QStringLiteral("org.kde.KDEPlatformTheme"),
0052                                               QStringLiteral("refreshFonts"),
0053                                               this,
0054                                               SLOT(notifyWatchersConfigurationChange()));
0055 #endif
0056 
0057         connect(qGuiApp, &QGuiApplication::fontDatabaseChanged, this, &StyleSingleton::notifyWatchersConfigurationChange);
0058         connect(qGuiApp, &QGuiApplication::fontChanged, this, &StyleSingleton::notifyWatchersConfigurationChange);
0059 
0060         /* QtTextRendering uses less memory, so use it in low power mode.
0061          *
0062          * For scale factors greater than 2, native rendering doesn't actually do much.
0063          * Does native rendering even work when scaleFactor >= 2?
0064          *
0065          * NativeTextRendering is still distorted sometimes with fractional scale
0066          * factors, despite https://bugreports.qt.io/browse/QTBUG-67007 being closed.
0067          * 1.5x scaling looks generally OK, but there are occasional and difficult to
0068          * reproduce issues with all fractional scale factors.
0069          */
0070         qreal devicePixelRatio = qGuiApp->devicePixelRatio();
0071 #ifndef Q_OS_ANDROID
0072         QQuickWindow::TextRenderType defaultTextRenderType = (int(devicePixelRatio) == devicePixelRatio //
0073                                                                   ? QQuickWindow::NativeTextRendering
0074                                                                   : QQuickWindow::QtTextRendering);
0075 
0076         // Allow setting the text rendering type with an environment variable
0077         QByteArrayList validInputs = {"qttextrendering", "qtrendering", "nativetextrendering", "nativerendering"};
0078         QByteArray input = qgetenv("QT_QUICK_DEFAULT_TEXT_RENDER_TYPE").toLower();
0079         if (validInputs.contains(input)) {
0080             if (input == validInputs[0] || input == validInputs[1]) {
0081                 defaultTextRenderType = QQuickWindow::QtTextRendering;
0082             } else {
0083                 defaultTextRenderType = QQuickWindow::NativeTextRendering;
0084             }
0085         }
0086 
0087         QQuickWindow::setTextRenderType(defaultTextRenderType);
0088 #else
0089         // Native rendering on android is broken, so prefer Qt rendering in
0090         // this case.
0091         QQuickWindow::setTextRenderType(QQuickWindow::QtTextRendering);
0092 #endif
0093 
0094         smallFont = loadSmallFont();
0095     }
0096 
0097     QFont loadSmallFont() const
0098     {
0099         KSharedConfigPtr ptr = KSharedConfig::openConfig();
0100         KConfigGroup general(ptr->group("general"));
0101 
0102         return general.readEntry("smallestReadableFont", []() {
0103             auto smallFont = qApp->font();
0104 #ifndef Q_OS_WIN
0105             if (smallFont.pixelSize() != -1) {
0106                 smallFont.setPixelSize(smallFont.pixelSize() - 2);
0107             } else {
0108                 smallFont.setPointSize(smallFont.pointSize() - 2);
0109             }
0110 #endif
0111             return smallFont;
0112         }());
0113     }
0114 
0115     void refresh()
0116     {
0117         m_cache.clear();
0118         buttonScheme = KColorScheme(QPalette::Active, KColorScheme::ColorSet::Button);
0119 
0120         notifyWatchersPaletteChange();
0121     }
0122 
0123     Colors loadColors(Kirigami::PlatformTheme::ColorSet cs, QPalette::ColorGroup group)
0124     {
0125         const auto key = qMakePair(cs, group);
0126         auto it = m_cache.constFind(key);
0127         if (it != m_cache.constEnd())
0128             return *it;
0129 
0130         using Kirigami::PlatformTheme;
0131 
0132         KColorScheme::ColorSet set;
0133 
0134         switch (cs) {
0135         case PlatformTheme::Button:
0136             set = KColorScheme::ColorSet::Button;
0137             break;
0138         case PlatformTheme::Selection:
0139             set = KColorScheme::ColorSet::Selection;
0140             break;
0141         case PlatformTheme::Tooltip:
0142             set = KColorScheme::ColorSet::Tooltip;
0143             break;
0144         case PlatformTheme::View:
0145             set = KColorScheme::ColorSet::View;
0146             break;
0147         case PlatformTheme::Complementary:
0148             set = KColorScheme::ColorSet::Complementary;
0149             break;
0150         case PlatformTheme::Header:
0151             set = KColorScheme::ColorSet::Header;
0152             break;
0153         case PlatformTheme::Window:
0154         default:
0155             set = KColorScheme::ColorSet::Window;
0156         }
0157 
0158         // HACK/FIXME: Working around the fact that KColorScheme changes the selection background color when inactive by default.
0159         // Yes, this is horrible.
0160         QPalette::ColorGroup selectionGroup = group == QPalette::Inactive ? QPalette::Active : group;
0161         Colors ret = {{}, KColorScheme(selectionGroup, KColorScheme::ColorSet::Selection), KColorScheme(group, set)};
0162 
0163         QPalette pal;
0164         for (auto state : {QPalette::Active, QPalette::Inactive, QPalette::Disabled}) {
0165             pal.setBrush(state, QPalette::WindowText, ret.scheme.foreground());
0166             pal.setBrush(state, QPalette::Window, ret.scheme.background());
0167             pal.setBrush(state, QPalette::Base, ret.scheme.background());
0168             pal.setBrush(state, QPalette::Text, ret.scheme.foreground());
0169             pal.setBrush(state, QPalette::Button, ret.scheme.background());
0170             pal.setBrush(state, QPalette::ButtonText, ret.scheme.foreground());
0171             pal.setBrush(state, QPalette::Highlight, ret.selectionScheme.background());
0172             pal.setBrush(state, QPalette::HighlightedText, ret.selectionScheme.foreground());
0173             pal.setBrush(state, QPalette::ToolTipBase, ret.scheme.background());
0174             pal.setBrush(state, QPalette::ToolTipText, ret.scheme.foreground());
0175 
0176             pal.setColor(state, QPalette::Light, ret.scheme.shade(KColorScheme::LightShade));
0177             pal.setColor(state, QPalette::Midlight, ret.scheme.shade(KColorScheme::MidlightShade));
0178             pal.setColor(state, QPalette::Mid, ret.scheme.shade(KColorScheme::MidShade));
0179             pal.setColor(state, QPalette::Dark, ret.scheme.shade(KColorScheme::DarkShade));
0180             pal.setColor(state, QPalette::Shadow, QColor(0, 0, 0, 51 /* 20% */)); // ret.scheme.shade(KColorScheme::ShadowShade));
0181 
0182             pal.setBrush(state, QPalette::AlternateBase, ret.scheme.background(KColorScheme::AlternateBackground));
0183             pal.setBrush(state, QPalette::Link, ret.scheme.foreground(KColorScheme::LinkText));
0184             pal.setBrush(state, QPalette::LinkVisited, ret.scheme.foreground(KColorScheme::VisitedText));
0185 
0186             pal.setBrush(state, QPalette::PlaceholderText, ret.scheme.foreground(KColorScheme::InactiveText));
0187             pal.setBrush(state,
0188                          QPalette::BrightText,
0189                          KColorUtils::hcyColor(KColorUtils::hue(pal.buttonText().color()),
0190                                                KColorUtils::chroma(pal.buttonText().color()),
0191                                                1 - KColorUtils::luma(pal.buttonText().color())));
0192         }
0193         ret.palette = pal;
0194         m_cache.insert(key, ret);
0195         return ret;
0196     }
0197 
0198     void notifyWatchersPaletteChange()
0199     {
0200         for (auto watcher : qAsConst(watchers)) {
0201             watcher->syncColors();
0202         }
0203     }
0204 
0205     Q_SLOT void notifyWatchersConfigurationChange()
0206     {
0207         smallFont = loadSmallFont();
0208         for (auto watcher : qAsConst(watchers)) {
0209             watcher->setSmallFont(smallFont);
0210             watcher->setDefaultFont(qApp->font());
0211         }
0212     }
0213 
0214     KColorScheme buttonScheme;
0215     QFont smallFont;
0216 
0217     QVector<PlasmaDesktopTheme *> watchers;
0218 
0219 private:
0220     QHash<QPair<Kirigami::PlatformTheme::ColorSet, QPalette::ColorGroup>, Colors> m_cache;
0221 };
0222 Q_GLOBAL_STATIC(StyleSingleton, s_style)
0223 
0224 PlasmaDesktopTheme::PlasmaDesktopTheme(QObject *parent)
0225     : PlatformTheme(parent)
0226 {
0227     // TODO: MOVE THIS SOMEWHERE ELSE
0228     m_lowPowerHardware = QByteArrayList{"1", "true"}.contains(qgetenv("KIRIGAMI_LOWPOWER_HARDWARE").toLower());
0229 
0230     // We don't use KIconLoader on Android so we don't support recoloring there
0231 #ifndef Q_OS_ANDROID
0232     setSupportsIconColoring(true);
0233 #endif
0234 
0235     auto parentItem = qobject_cast<QQuickItem *>(parent);
0236     if (parentItem) {
0237         connect(parentItem, &QQuickItem::enabledChanged, this, &PlasmaDesktopTheme::syncColors);
0238         connect(parentItem, &QQuickItem::visibleChanged, this, &PlasmaDesktopTheme::syncColors);
0239         connect(parentItem, &QQuickItem::windowChanged, this, &PlasmaDesktopTheme::syncWindow);
0240     }
0241 
0242     s_style->watchers.append(this);
0243 
0244     setDefaultFont(qGuiApp->font());
0245     setSmallFont(s_style->smallFont);
0246 
0247     syncWindow();
0248     syncColors();
0249 }
0250 
0251 PlasmaDesktopTheme::~PlasmaDesktopTheme()
0252 {
0253     s_style->watchers.removeOne(this);
0254 }
0255 
0256 void PlasmaDesktopTheme::syncWindow()
0257 {
0258     if (m_window) {
0259         disconnect(m_window.data(), &QWindow::activeChanged, this, &PlasmaDesktopTheme::syncColors);
0260     }
0261 
0262     QWindow *window = nullptr;
0263 
0264     auto parentItem = qobject_cast<QQuickItem *>(parent());
0265     if (parentItem) {
0266         QQuickWindow *qw = parentItem->window();
0267 
0268         window = QQuickRenderControl::renderWindowFor(qw);
0269         if (!window) {
0270             window = qw;
0271         }
0272         if (qw) {
0273             connect(qw, &QQuickWindow::sceneGraphInitialized, this, &PlasmaDesktopTheme::syncWindow);
0274         }
0275     }
0276     m_window = window;
0277 
0278     if (window) {
0279         connect(m_window.data(), &QWindow::activeChanged, this, &PlasmaDesktopTheme::syncColors);
0280         syncColors();
0281     }
0282 }
0283 
0284 QIcon PlasmaDesktopTheme::iconFromTheme(const QString &name, const QColor &customColor)
0285 {
0286 #ifndef Q_OS_ANDROID
0287     if (customColor != Qt::transparent) {
0288         KIconColors colors;
0289         colors.setText(customColor);
0290         return KDE::icon(name, colors);
0291     } else {
0292         return KDE::icon(name);
0293     }
0294 
0295 #else
0296     // On Android we don't want to use the KIconThemes-based loader since that appears to be broken
0297     return QIcon::fromTheme(name);
0298 #endif
0299 }
0300 
0301 void PlasmaDesktopTheme::syncColors()
0302 {
0303     QPalette::ColorGroup group = (QPalette::ColorGroup)colorGroup();
0304     auto parentItem = qobject_cast<QQuickItem *>(parent());
0305     if (parentItem) {
0306         if (!parentItem->isEnabled()) {
0307             group = QPalette::Disabled;
0308         } else if (m_window && !m_window->isActive() && m_window->isExposed()) {
0309             // Why also checking the window is exposed?
0310             // in the case of QQuickWidget the window() will never be active
0311             // and the widgets will always have the inactive palette.
0312             // better to always show it active than always show it inactive
0313             group = QPalette::Inactive;
0314         }
0315     }
0316 
0317     const auto colors = s_style->loadColors(colorSet(), group);
0318 
0319     // foreground
0320     setTextColor(colors.scheme.foreground(KColorScheme::NormalText).color());
0321     setDisabledTextColor(colors.scheme.foreground(KColorScheme::InactiveText).color());
0322     setHighlightedTextColor(colors.selectionScheme.foreground(KColorScheme::NormalText).color());
0323     setActiveTextColor(colors.scheme.foreground(KColorScheme::ActiveText).color());
0324     setLinkColor(colors.scheme.foreground(KColorScheme::LinkText).color());
0325     setVisitedLinkColor(colors.scheme.foreground(KColorScheme::VisitedText).color());
0326     setNegativeTextColor(colors.scheme.foreground(KColorScheme::NegativeText).color());
0327     setNeutralTextColor(colors.scheme.foreground(KColorScheme::NeutralText).color());
0328     setPositiveTextColor(colors.scheme.foreground(KColorScheme::PositiveText).color());
0329 
0330     // background
0331     setHighlightColor(colors.selectionScheme.background(KColorScheme::NormalBackground).color());
0332     setBackgroundColor(colors.scheme.background(KColorScheme::NormalBackground).color());
0333 
0334     // HACK: It's awful, but people sometimes complain about their color scheme not working well with the theme.
0335     // This is because I'm using colors that weren't used before and lots of themes have bad colors for previously unused colors.
0336     QColor alternateBackgroundOriginalColor = colors.scheme.background(KColorScheme::AlternateBackground).color();
0337     // #bdc3c7 is the old default for the Breeze color scheme.
0338     // #4d4d4d is the old default for the Breeze Dark color scheme.
0339     // Most color schemes use one of these 2 colors.
0340     if (colorSet() == ColorSet::Button && (alternateBackgroundOriginalColor == QColor("#bdc3c7") || alternateBackgroundOriginalColor == QColor("#4d4d4d"))) {
0341         setAlternateBackgroundColor(KColorUtils::tint(backgroundColor(), highlightColor(), 0.4));
0342     } else {
0343         setAlternateBackgroundColor(alternateBackgroundOriginalColor);
0344     }
0345     setActiveBackgroundColor(colors.scheme.background(KColorScheme::ActiveBackground).color());
0346     setLinkBackgroundColor(colors.scheme.background(KColorScheme::LinkBackground).color());
0347     setVisitedLinkBackgroundColor(colors.scheme.background(KColorScheme::VisitedBackground).color());
0348     setNegativeBackgroundColor(colors.scheme.background(KColorScheme::NegativeBackground).color());
0349     setNeutralBackgroundColor(colors.scheme.background(KColorScheme::NeutralBackground).color());
0350     setPositiveBackgroundColor(colors.scheme.background(KColorScheme::PositiveBackground).color());
0351 
0352     // decoration
0353     setHoverColor(colors.scheme.decoration(KColorScheme::HoverColor).color());
0354     setFocusColor(colors.scheme.decoration(KColorScheme::FocusColor).color());
0355 
0356     // Breeze QQC2 style colors
0357     const QColor &buttonTextColor = s_style->buttonScheme.foreground(KColorScheme::NormalText).color();
0358     const QColor &buttonBackgroundColor = s_style->buttonScheme.background(KColorScheme::NormalBackground).color();
0359     auto separatorColor = [](const QColor &bg, const QColor &fg, const qreal baseRatio = 0.2) {
0360         return KColorUtils::luma(bg) > 0.5 ? KColorUtils::mix(bg, fg, baseRatio) : KColorUtils::mix(bg, fg, baseRatio / 2);
0361     };
0362 
0363     m_buttonSeparatorColor = separatorColor(buttonBackgroundColor, buttonTextColor, 0.3);
0364 
0365     switch (colorSet()) {
0366         // case ColorSet::View:
0367         // case ColorSet::Window:
0368     case ColorSet::Button:
0369         m_separatorColor = m_buttonSeparatorColor;
0370         break;
0371     case ColorSet::Selection:
0372         m_separatorColor = focusColor();
0373         break;
0374         // case ColorSet::Tooltip:
0375         // case ColorSet::Complementary:
0376         // case ColorSet::Header:
0377     default:
0378         m_separatorColor = separatorColor(backgroundColor(), textColor());
0379     }
0380 }
0381 
0382 bool PlasmaDesktopTheme::event(QEvent *event)
0383 {
0384     if (event->type() == Kirigami::PlatformThemeEvents::DataChangedEvent::type) {
0385         syncColors();
0386     }
0387 
0388     if (event->type() == Kirigami::PlatformThemeEvents::ColorSetChangedEvent::type) {
0389         syncColors();
0390     }
0391 
0392     if (event->type() == Kirigami::PlatformThemeEvents::ColorGroupChangedEvent::type) {
0393         syncColors();
0394     }
0395 
0396     return PlatformTheme::event(event);
0397 }
0398 
0399 // Breeze QQC2 style colors
0400 QColor PlasmaDesktopTheme::separatorColor() const
0401 {
0402     return m_separatorColor;
0403 }
0404 
0405 QColor PlasmaDesktopTheme::buttonSeparatorColor() const
0406 {
0407     return m_buttonSeparatorColor;
0408 }
0409 
0410 bool PlasmaDesktopTheme::lowPowerHardware() const
0411 {
0412     return m_lowPowerHardware;
0413 }
0414 
0415 #include "plasmadesktoptheme.moc"