File indexing completed on 2023-10-01 04:11:46

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "theme.h"
0008 #include "private/svg_p.h"
0009 #include "private/theme_p.h"
0010 
0011 #include <QFile>
0012 #include <QFileInfo>
0013 #include <QFontDatabase>
0014 #include <QFontMetrics>
0015 #include <QMutableListIterator>
0016 #include <QPair>
0017 #include <QStringBuilder>
0018 #include <QThread>
0019 #include <QTimer>
0020 
0021 #include "config-plasma.h"
0022 
0023 #include <KColorScheme>
0024 #include <KConfigGroup>
0025 #include <KDirWatch>
0026 #include <KImageCache>
0027 #include <KWindowEffects>
0028 #include <QDebug>
0029 #include <QStandardPaths>
0030 
0031 #include "debug_p.h"
0032 
0033 namespace Plasma
0034 {
0035 Theme::Theme(QObject *parent)
0036     : QObject(parent)
0037 {
0038     if (!ThemePrivate::globalTheme) {
0039         ThemePrivate::globalTheme = new ThemePrivate;
0040         ThemePrivate::globalTheme->settingsChanged(false);
0041         if (QCoreApplication::instance()) {
0042             connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, ThemePrivate::globalTheme, &ThemePrivate::onAppExitCleanup);
0043         }
0044     }
0045     ThemePrivate::globalTheme->ref.ref();
0046     d = ThemePrivate::globalTheme;
0047 
0048     connect(d, &ThemePrivate::themeChanged, this, &Theme::themeChanged);
0049     connect(d, &ThemePrivate::defaultFontChanged, this, &Theme::defaultFontChanged);
0050     connect(d, &ThemePrivate::smallestFontChanged, this, &Theme::smallestFontChanged);
0051 }
0052 
0053 Theme::Theme(const QString &themeName, QObject *parent)
0054     : QObject(parent)
0055 {
0056     auto &priv = ThemePrivate::themes[themeName];
0057     if (!priv) {
0058         priv = new ThemePrivate;
0059         if (QCoreApplication::instance()) {
0060             connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, priv, &ThemePrivate::onAppExitCleanup);
0061         }
0062     }
0063 
0064     priv->ref.ref();
0065     d = priv;
0066 
0067     // turn off caching so we don't accidentally trigger unnecessary disk activity at this point
0068     bool useCache = d->cacheTheme;
0069     d->cacheTheme = false;
0070     d->setThemeName(themeName, false, false);
0071     d->cacheTheme = useCache;
0072     d->fixedName = true;
0073     connect(d, &ThemePrivate::themeChanged, this, &Theme::themeChanged);
0074 }
0075 
0076 Theme::~Theme()
0077 {
0078     if (d == ThemePrivate::globalTheme) {
0079         if (!d->ref.deref()) {
0080             disconnect(ThemePrivate::globalTheme, nullptr, this, nullptr);
0081             delete ThemePrivate::globalTheme;
0082             ThemePrivate::globalTheme = nullptr;
0083             d = nullptr;
0084         }
0085     } else {
0086         if (!d->ref.deref()) {
0087             delete ThemePrivate::themes.take(d->themeName);
0088         }
0089     }
0090 }
0091 
0092 void Theme::setThemeName(const QString &themeName)
0093 {
0094     if (d->themeName == themeName) {
0095         return;
0096     }
0097 
0098     if (d != ThemePrivate::globalTheme) {
0099         disconnect(QCoreApplication::instance(), nullptr, d, nullptr);
0100         if (!d->ref.deref()) {
0101             delete ThemePrivate::themes.take(d->themeName);
0102         }
0103 
0104         auto &priv = ThemePrivate::themes[themeName];
0105         if (!priv) {
0106             priv = new ThemePrivate;
0107             if (QCoreApplication::instance()) {
0108                 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, priv, &ThemePrivate::onAppExitCleanup);
0109             }
0110         }
0111         priv->ref.ref();
0112         d = priv;
0113         connect(d, &ThemePrivate::themeChanged, this, &Theme::themeChanged);
0114     }
0115 
0116     d->setThemeName(themeName, true, true);
0117 }
0118 
0119 QString Theme::themeName() const
0120 {
0121     return d->themeName;
0122 }
0123 
0124 QString Theme::imagePath(const QString &name) const
0125 {
0126     // look for a compressed svg file in the theme
0127     if (name.contains(QLatin1String("../")) || name.isEmpty()) {
0128         // we don't support relative paths
0129         // qCDebug(LOG_PLASMA) << "Theme says: bad image path " << name;
0130         return QString();
0131     }
0132 
0133     const QString svgzName = name % QLatin1String(".svgz");
0134     QString path = d->findInTheme(svgzName, d->themeName);
0135 
0136     if (path.isEmpty()) {
0137         // try for an uncompressed svg file
0138         const QString svgName = name % QLatin1String(".svg");
0139         path = d->findInTheme(svgName, d->themeName);
0140 
0141         // search in fallback themes if necessary
0142         for (int i = 0; path.isEmpty() && i < d->fallbackThemes.count(); ++i) {
0143             if (d->themeName == d->fallbackThemes[i]) {
0144                 continue;
0145             }
0146 
0147             // try a compressed svg file in the fallback theme
0148             path = d->findInTheme(svgzName, d->fallbackThemes[i]);
0149 
0150             if (path.isEmpty()) {
0151                 // try an uncompressed svg file in the fallback theme
0152                 path = d->findInTheme(svgName, d->fallbackThemes[i]);
0153             }
0154         }
0155     }
0156 
0157     return path;
0158 }
0159 
0160 QString Theme::backgroundPath(const QString &image) const
0161 {
0162     return d->imagePath(themeName(), QStringLiteral("/appbackgrounds/"), image);
0163 }
0164 
0165 QString Theme::styleSheet(const QString &css) const
0166 {
0167     return d->processStyleSheet(css, Svg::Status::Normal);
0168 }
0169 
0170 QPalette Theme::palette() const
0171 {
0172     return d->palette;
0173 }
0174 
0175 QPalette Theme::globalPalette()
0176 {
0177     if (!ThemePrivate::globalTheme) {
0178         ThemePrivate::globalTheme = new ThemePrivate;
0179         ThemePrivate::globalTheme->settingsChanged(false);
0180     }
0181     return ThemePrivate::globalTheme->palette;
0182 }
0183 
0184 QString Theme::wallpaperPath(const QSize &size) const
0185 {
0186     QString fullPath;
0187     QString image = d->defaultWallpaperTheme + QStringLiteral("/contents/images/%1x%2") + d->defaultWallpaperSuffix;
0188     QString defaultImage = image.arg(d->defaultWallpaperWidth).arg(d->defaultWallpaperHeight);
0189 
0190     if (size.isValid()) {
0191         // try to customize the paper to the size requested
0192         // TODO: this should do better than just fallback to the default size.
0193         //      a "best fit" matching would be far better, so we don't end
0194         //      up returning a 1920x1200 wallpaper for a 640x480 request ;)
0195         image = image.arg(size.width()).arg(size.height());
0196     } else {
0197         image = defaultImage;
0198     }
0199 
0200     // TODO: the theme's wallpaper overrides regularly installed wallpapers.
0201     //      should it be possible for user installed (e.g. locateLocal) wallpapers
0202     //      to override the theme?
0203     if (d->hasWallpapers) {
0204         // check in the theme first
0205         fullPath = d->findInTheme(QLatin1String("wallpapers/") % image, d->themeName);
0206 
0207         if (fullPath.isEmpty()) {
0208             fullPath = d->findInTheme(QLatin1String("wallpapers/") % defaultImage, d->themeName);
0209         }
0210     }
0211 
0212     if (fullPath.isEmpty()) {
0213         // we failed to find it in the theme, so look in the standard directories
0214         // qCDebug(LOG_PLASMA) << "looking for" << image;
0215         fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + image);
0216     }
0217 
0218     if (fullPath.isEmpty()) {
0219         // we still failed to find it in the theme, so look for the default in
0220         // the standard directories
0221         // qCDebug(LOG_PLASMA) << "looking for" << defaultImage;
0222         fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + defaultImage);
0223     }
0224 
0225     return fullPath;
0226 }
0227 
0228 QString Theme::wallpaperPathForSize(int width, int height) const
0229 {
0230     return Plasma::Theme::wallpaperPath(QSize(width, height));
0231 }
0232 
0233 bool Theme::currentThemeHasImage(const QString &name) const
0234 {
0235     if (name.contains(QLatin1String("../"))) {
0236         // we don't support relative paths
0237         return false;
0238     }
0239 
0240     QString path = d->findInTheme(name % QLatin1String(".svgz"), d->themeName);
0241     if (path.isEmpty()) {
0242         path = d->findInTheme(name % QLatin1String(".svg"), d->themeName);
0243     }
0244     return path.contains(QLatin1String("/" PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % d->themeName);
0245 }
0246 
0247 KSharedConfigPtr Theme::colorScheme() const
0248 {
0249     return d->colors;
0250 }
0251 
0252 QColor Theme::color(ColorRole role, ColorGroup group) const
0253 {
0254     return d->color(role, group);
0255 }
0256 
0257 void Theme::setUseGlobalSettings(bool useGlobal)
0258 {
0259     if (d->useGlobal == useGlobal) {
0260         return;
0261     }
0262 
0263     d->useGlobal = useGlobal;
0264     d->cfg = KConfigGroup();
0265     d->themeName.clear();
0266     d->settingsChanged(true);
0267 }
0268 
0269 bool Theme::useGlobalSettings() const
0270 {
0271     return d->useGlobal;
0272 }
0273 
0274 bool Theme::findInCache(const QString &key, QPixmap &pix, unsigned int lastModified)
0275 {
0276     // TODO KF6: Make lastModified non-optional.
0277     if (lastModified == 0) {
0278         qCWarning(LOG_PLASMA) << "findInCache with a lastModified timestamp of 0 is deprecated";
0279         return false;
0280     }
0281 
0282     if (!d->useCache()) {
0283         return false;
0284     }
0285 
0286     if (lastModified > uint(d->pixmapCache->lastModifiedTime().toSecsSinceEpoch())) {
0287         return false;
0288     }
0289 
0290     const QString id = d->keysToCache.value(key);
0291     const auto it = d->pixmapsToCache.constFind(id);
0292     if (it != d->pixmapsToCache.constEnd()) {
0293         pix = *it;
0294         return !pix.isNull();
0295     }
0296 
0297     QPixmap temp;
0298     if (d->pixmapCache->findPixmap(key, &temp) && !temp.isNull()) {
0299         pix = temp;
0300         return true;
0301     }
0302 
0303     return false;
0304 }
0305 
0306 void Theme::insertIntoCache(const QString &key, const QPixmap &pix)
0307 {
0308     if (d->useCache()) {
0309         d->pixmapCache->insertPixmap(key, pix);
0310     }
0311 }
0312 
0313 void Theme::insertIntoCache(const QString &key, const QPixmap &pix, const QString &id)
0314 {
0315     if (d->useCache()) {
0316         d->pixmapsToCache[id] = pix;
0317         d->keysToCache[key] = id;
0318         d->idsToCache[id] = key;
0319 
0320         // always start timer in d->pixmapSaveTimer's thread
0321         QMetaObject::invokeMethod(d->pixmapSaveTimer, "start", Qt::QueuedConnection);
0322     }
0323 }
0324 
0325 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 78)
0326 bool Theme::findInRectsCache(const QString &image, const QString &element, QRectF &rect) const
0327 {
0328     if (!d->useCache()) {
0329         return false;
0330     }
0331 
0332     bool ok = false;
0333     uint id = element.toLong(&ok);
0334     if (!ok) {
0335         return false;
0336     }
0337 
0338     return SvgRectsCache::instance()->findElementRect(id, image, rect);
0339 }
0340 
0341 QStringList Theme::listCachedRectKeys(const QString &image) const
0342 {
0343     if (!d->useCache()) {
0344         return QStringList();
0345     }
0346 
0347     return SvgRectsCache::instance()->cachedKeysForPath(image);
0348 }
0349 
0350 void Theme::insertIntoRectsCache(const QString &image, const QString &element, const QRectF &rect)
0351 {
0352     if (!d->useCache()) {
0353         return;
0354     }
0355 
0356     bool ok = false;
0357     uint id = element.toLong(&ok);
0358     if (!ok) {
0359         return;
0360     }
0361 
0362     uint secs = QDateTime::currentSecsSinceEpoch();
0363     SvgRectsCache::instance()->insert(id, image, rect, secs);
0364 }
0365 
0366 void Theme::invalidateRectsCache(const QString &image)
0367 {
0368     SvgRectsCache::instance()->dropImageFromCache(image);
0369 }
0370 
0371 void Theme::releaseRectsCache(const QString &image)
0372 {
0373     Q_UNUSED(image);
0374     // No op: the internal svg cache always writes the invalid elements in the proper place
0375 }
0376 #endif
0377 
0378 void Theme::setCacheLimit(int kbytes)
0379 {
0380     d->cacheSize = kbytes;
0381     delete d->pixmapCache;
0382     d->pixmapCache = nullptr;
0383 }
0384 
0385 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 67)
0386 KPluginInfo Theme::pluginInfo() const
0387 {
0388     return KPluginInfo(d->pluginMetaData);
0389 }
0390 #endif
0391 
0392 KPluginMetaData Theme::metadata() const
0393 {
0394     return d->pluginMetaData;
0395 }
0396 
0397 QFont Theme::defaultFont() const
0398 {
0399     return QGuiApplication::font();
0400 }
0401 
0402 QFont Theme::smallestFont() const
0403 {
0404     return QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
0405 }
0406 
0407 QSizeF Theme::mSize(const QFont &font) const
0408 {
0409     return QFontMetrics(font).boundingRect(QStringLiteral("M")).size();
0410 }
0411 
0412 bool Theme::backgroundContrastEnabled() const
0413 {
0414     return d->backgroundContrastEnabled;
0415 }
0416 
0417 bool Theme::adaptiveTransparencyEnabled() const
0418 {
0419     return d->adaptiveTransparencyEnabled;
0420 }
0421 
0422 qreal Theme::backgroundContrast() const
0423 {
0424     if (qIsNaN(d->backgroundContrast)) {
0425         // Make up sensible default values, based on the background color
0426         // If we're using a dark background color, darken the background
0427         if (qGray(color(Plasma::Theme::BackgroundColor).rgb()) < 127) {
0428             return 0.45;
0429             // for a light theme lighten up the background
0430         } else {
0431             return 0.3;
0432         }
0433     }
0434     return d->backgroundContrast;
0435 }
0436 
0437 qreal Theme::backgroundIntensity() const
0438 {
0439     if (qIsNaN(d->backgroundIntensity)) {
0440         if (qGray(color(Plasma::Theme::BackgroundColor).rgb()) < 127) {
0441             return 0.45;
0442         } else {
0443             return 1.9;
0444         }
0445     }
0446     return d->backgroundIntensity;
0447 }
0448 
0449 qreal Theme::backgroundSaturation() const
0450 {
0451     if (qIsNaN(d->backgroundSaturation)) {
0452         return 1.7;
0453     }
0454     return d->backgroundSaturation;
0455 }
0456 
0457 bool Theme::blurBehindEnabled() const
0458 {
0459     return d->blurBehindEnabled;
0460 }
0461 
0462 }
0463 
0464 #include "moc_theme.cpp"