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"