File indexing completed on 2025-03-16 05:05:14
0001 /* 0002 SPDX-FileCopyrightText: 2018 Michail Vourlakos <mvourlakos@gmail.com> 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #include "theme.h" 0007 0008 // local 0009 #include "lattecorona.h" 0010 #include "panelbackground.h" 0011 #include "../../layouts/importer.h" 0012 #include "../../view/panelshadows_p.h" 0013 #include "../../wm/schemecolors.h" 0014 #include "../../tools/commontools.h" 0015 0016 // Qt 0017 #include <QDebug> 0018 #include <QDir> 0019 #include <QPainter> 0020 0021 // KDE 0022 #include <KDirWatch> 0023 #include <KConfigGroup> 0024 #include <KSharedConfig> 0025 0026 // X11 0027 #include <KWindowSystem> 0028 0029 #define DEFAULTCOLORSCHEME "default.colors" 0030 #define REVERSEDCOLORSCHEME "reversed.colors" 0031 0032 namespace Latte { 0033 namespace PlasmaExtended { 0034 0035 Theme::Theme(KSharedConfig::Ptr config, QObject *parent) : 0036 QObject(parent), 0037 m_themeGroup(KConfigGroup(config, QStringLiteral("PlasmaThemeExtended"))), 0038 m_backgroundTopEdge(new PanelBackground(Plasma::Types::TopEdge, this)), 0039 m_backgroundLeftEdge(new PanelBackground(Plasma::Types::LeftEdge, this)), 0040 m_backgroundBottomEdge(new PanelBackground(Plasma::Types::BottomEdge, this)), 0041 m_backgroundRightEdge(new PanelBackground(Plasma::Types::RightEdge, this)) 0042 { 0043 qmlRegisterTypes(); 0044 0045 m_corona = qobject_cast<Latte::Corona *>(parent); 0046 0047 //! compositing tracking 0048 if (KWindowSystem::isPlatformWayland()) { 0049 //! TODO: Wayland compositing active 0050 m_compositing = true; 0051 } else { 0052 connect(KWindowSystem::self(), &KWindowSystem::compositingChanged 0053 , this, [&](bool enabled) { 0054 if (m_compositing == enabled) 0055 return; 0056 0057 m_compositing = enabled; 0058 emit compositingChanged(); 0059 }); 0060 0061 m_compositing = KWindowSystem::compositingActive(); 0062 } 0063 //! 0064 0065 loadConfig(); 0066 0067 connect(this, &Theme::compositingChanged, this, &Theme::updateBackgrounds); 0068 connect(this, &Theme::outlineWidthChanged, this, &Theme::saveConfig); 0069 0070 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::load); 0071 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::themeChanged); 0072 } 0073 0074 void Theme::load() 0075 { 0076 loadThemePaths(); 0077 updateBackgrounds(); 0078 updateMarginsAreaValues(); 0079 } 0080 0081 Theme::~Theme() 0082 { 0083 saveConfig(); 0084 0085 m_defaultScheme->deleteLater(); 0086 m_reversedScheme->deleteLater(); 0087 } 0088 0089 bool Theme::hasShadow() const 0090 { 0091 return m_hasShadow; 0092 } 0093 0094 bool Theme::isLightTheme() const 0095 { 0096 return m_isLightTheme; 0097 } 0098 0099 bool Theme::isDarkTheme() const 0100 { 0101 return !m_isLightTheme; 0102 } 0103 0104 int Theme::outlineWidth() const 0105 { 0106 return m_outlineWidth; 0107 } 0108 0109 void Theme::setOutlineWidth(int width) 0110 { 0111 if (m_outlineWidth == width) { 0112 return; 0113 } 0114 0115 m_outlineWidth = width; 0116 emit outlineWidthChanged(); 0117 } 0118 0119 int Theme::marginsAreaTop() const 0120 { 0121 return m_marginsAreaTop; 0122 } 0123 0124 int Theme::marginsAreaLeft() const 0125 { 0126 return m_marginsAreaLeft; 0127 } 0128 0129 int Theme::marginsAreaBottom() const 0130 { 0131 return m_marginsAreaBottom; 0132 } 0133 0134 int Theme::marginsAreaRight() const 0135 { 0136 return m_marginsAreaRight; 0137 } 0138 0139 0140 PanelBackground *Theme::backgroundTopEdge() const 0141 { 0142 return m_backgroundTopEdge; 0143 } 0144 0145 PanelBackground *Theme::backgroundLeftEdge() const 0146 { 0147 return m_backgroundLeftEdge; 0148 } 0149 0150 PanelBackground *Theme::backgroundBottomEdge() const 0151 { 0152 return m_backgroundBottomEdge; 0153 } 0154 0155 PanelBackground *Theme::backgroundRightEdge() const 0156 { 0157 return m_backgroundRightEdge; 0158 } 0159 0160 WindowSystem::SchemeColors *Theme::defaultTheme() const 0161 { 0162 return m_defaultScheme; 0163 } 0164 0165 WindowSystem::SchemeColors *Theme::lightTheme() const 0166 { 0167 return m_isLightTheme ? m_defaultScheme : m_reversedScheme; 0168 } 0169 0170 WindowSystem::SchemeColors *Theme::darkTheme() const 0171 { 0172 return !m_isLightTheme ? m_defaultScheme : m_reversedScheme; 0173 } 0174 0175 0176 void Theme::setOriginalSchemeFile(const QString &file) 0177 { 0178 if (m_originalSchemePath == file) { 0179 return; 0180 } 0181 0182 m_originalSchemePath = file; 0183 0184 qDebug() << "plasma theme original colors ::: " << m_originalSchemePath; 0185 0186 updateDefaultScheme(); 0187 updateReversedScheme(); 0188 0189 loadThemeLightness(); 0190 0191 emit themeChanged(); 0192 } 0193 0194 //! WM records need to be updated based on the colors that 0195 //! plasma will use in order to be consistent. Such an example 0196 //! are the Breeze color schemes that have different values for 0197 //! WM and the plasma theme records 0198 void Theme::updateDefaultScheme() 0199 { 0200 QString defaultFilePath = m_extendedThemeDir.path() + "/" + DEFAULTCOLORSCHEME; 0201 if (QFileInfo(defaultFilePath).exists()) { 0202 QFile(defaultFilePath).remove(); 0203 } 0204 0205 QFile(m_originalSchemePath).copy(defaultFilePath); 0206 m_defaultSchemePath = defaultFilePath; 0207 0208 updateDefaultSchemeValues(); 0209 0210 if (m_defaultScheme) { 0211 disconnect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness); 0212 m_defaultScheme->deleteLater(); 0213 } 0214 0215 m_defaultScheme = new WindowSystem::SchemeColors(this, m_defaultSchemePath, true); 0216 connect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness); 0217 0218 qDebug() << "plasma theme default colors ::: " << m_defaultSchemePath; 0219 } 0220 0221 void Theme::updateDefaultSchemeValues() 0222 { 0223 //! update WM values based on original scheme 0224 KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath); 0225 KSharedConfigPtr defaultPtr = KSharedConfig::openConfig(m_defaultSchemePath); 0226 0227 if (originalPtr && defaultPtr) { 0228 KConfigGroup normalWindowGroup(originalPtr, "Colors:Window"); 0229 KConfigGroup defaultWMGroup(defaultPtr, "WM"); 0230 0231 defaultWMGroup.writeEntry("activeBackground", normalWindowGroup.readEntry("BackgroundNormal", QColor())); 0232 defaultWMGroup.writeEntry("activeForeground", normalWindowGroup.readEntry("ForegroundNormal", QColor())); 0233 0234 defaultWMGroup.sync(); 0235 } 0236 } 0237 0238 void Theme::updateReversedScheme() 0239 { 0240 QString reversedFilePath = m_extendedThemeDir.path() + "/" + REVERSEDCOLORSCHEME; 0241 0242 if (QFileInfo(reversedFilePath).exists()) { 0243 QFile(reversedFilePath).remove(); 0244 } 0245 0246 QFile(m_originalSchemePath).copy(reversedFilePath); 0247 m_reversedSchemePath = reversedFilePath; 0248 0249 updateReversedSchemeValues(); 0250 0251 if (m_reversedScheme) { 0252 m_reversedScheme->deleteLater(); 0253 } 0254 0255 m_reversedScheme = new WindowSystem::SchemeColors(this, m_reversedSchemePath, true); 0256 0257 qDebug() << "plasma theme reversed colors ::: " << m_reversedSchemePath; 0258 } 0259 0260 void Theme::updateReversedSchemeValues() 0261 { 0262 //! reverse values based on original scheme 0263 KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath); 0264 KSharedConfigPtr reversedPtr = KSharedConfig::openConfig(m_reversedSchemePath); 0265 0266 if (originalPtr && reversedPtr) { 0267 for (const auto &groupName : reversedPtr->groupList()) { 0268 if (groupName != "Colors:Button" && groupName != "Colors:Selection") { 0269 KConfigGroup reversedGroup(reversedPtr, groupName); 0270 0271 if (reversedGroup.keyList().contains("BackgroundNormal") 0272 && reversedGroup.keyList().contains("ForegroundNormal")) { 0273 //! reverse usual text/background values 0274 KConfigGroup originalGroup(originalPtr, groupName); 0275 0276 reversedGroup.writeEntry("BackgroundNormal", originalGroup.readEntry("ForegroundNormal", QColor())); 0277 reversedGroup.writeEntry("ForegroundNormal", originalGroup.readEntry("BackgroundNormal", QColor())); 0278 0279 reversedGroup.sync(); 0280 } 0281 } 0282 } 0283 0284 //! update WM group 0285 KConfigGroup reversedWMGroup(reversedPtr, "WM"); 0286 KConfigGroup normalWindowGroup(originalPtr, "Colors:Window"); 0287 0288 if (reversedWMGroup.keyList().contains("activeBackground") 0289 && reversedWMGroup.keyList().contains("activeForeground") 0290 && reversedWMGroup.keyList().contains("inactiveBackground") 0291 && reversedWMGroup.keyList().contains("inactiveForeground")) { 0292 //! reverse usual wm titlebar values 0293 KConfigGroup originalGroup(originalPtr, "WM"); 0294 reversedWMGroup.writeEntry("activeBackground", normalWindowGroup.readEntry("ForegroundNormal", QColor())); 0295 reversedWMGroup.writeEntry("activeForeground", normalWindowGroup.readEntry("BackgroundNormal", QColor())); 0296 reversedWMGroup.writeEntry("inactiveBackground", originalGroup.readEntry("inactiveForeground", QColor())); 0297 reversedWMGroup.writeEntry("inactiveForeground", originalGroup.readEntry("inactiveBackground", QColor())); 0298 reversedWMGroup.sync(); 0299 } 0300 0301 if (reversedWMGroup.keyList().contains("activeBlend") 0302 && reversedWMGroup.keyList().contains("inactiveBlend")) { 0303 KConfigGroup originalGroup(originalPtr, "WM"); 0304 reversedWMGroup.writeEntry("activeBlend", originalGroup.readEntry("inactiveBlend", QColor())); 0305 reversedWMGroup.writeEntry("inactiveBlend", originalGroup.readEntry("activeBlend", QColor())); 0306 reversedWMGroup.sync(); 0307 } 0308 0309 //! update scheme name 0310 QString originalSchemeName = WindowSystem::SchemeColors::schemeName(m_originalSchemePath); 0311 KConfigGroup generalGroup(reversedPtr, "General"); 0312 generalGroup.writeEntry("Name", originalSchemeName + "_reversed"); 0313 generalGroup.sync(); 0314 } 0315 } 0316 0317 void Theme::updateBackgrounds() 0318 { 0319 updateHasShadow(); 0320 0321 m_backgroundTopEdge->update(); 0322 m_backgroundLeftEdge->update(); 0323 m_backgroundBottomEdge->update(); 0324 m_backgroundRightEdge->update(); 0325 } 0326 0327 void Theme::updateHasShadow() 0328 { 0329 Plasma::Svg *svg = new Plasma::Svg(this); 0330 svg->setImagePath(QStringLiteral("widgets/panel-background")); 0331 svg->resize(); 0332 0333 QString cornerId = "shadow-topleft"; 0334 QImage corner = svg->image(svg->elementSize(cornerId), cornerId); 0335 0336 int fullTransparentPixels = 0; 0337 0338 for(int c=0; c<corner.width(); ++c) { 0339 for(int r=0; r<corner.height(); ++r) { 0340 QRgb *line = (QRgb *)corner.scanLine(r); 0341 QRgb point = line[c]; 0342 0343 if (qAlpha(point) == 0) { 0344 fullTransparentPixels++; 0345 } 0346 } 0347 } 0348 0349 int pixels = (corner.width() * corner.height()); 0350 0351 m_hasShadow = (fullTransparentPixels != pixels ); 0352 emit hasShadowChanged(); 0353 0354 qDebug() << " PLASMA THEME TOPLEFT SHADOW :: pixels : " << pixels << " transparent pixels" << fullTransparentPixels << " | HAS SHADOWS :" << m_hasShadow; 0355 0356 svg->deleteLater(); 0357 } 0358 0359 void Theme::loadThemePaths() 0360 { 0361 m_themePath = Layouts::Importer::standardPath("plasma/desktoptheme/" + m_theme.themeName()); 0362 0363 if (QDir(m_themePath+"/widgets").exists()) { 0364 m_themeWidgetsPath = m_themePath + "/widgets"; 0365 } else { 0366 m_themeWidgetsPath = Layouts::Importer::standardPath("plasma/desktoptheme/default/widgets"); 0367 } 0368 0369 qDebug() << "current plasma theme ::: " << m_theme.themeName(); 0370 qDebug() << "theme path ::: " << m_themePath; 0371 qDebug() << "theme widgets path ::: " << m_themeWidgetsPath; 0372 0373 //! clear kde connections 0374 for (auto &c : m_kdeConnections) { 0375 disconnect(c); 0376 } 0377 0378 //! assign color schemes 0379 QString themeColorScheme = m_themePath + "/colors"; 0380 0381 if (QFileInfo(themeColorScheme).exists()) { 0382 setOriginalSchemeFile(themeColorScheme); 0383 } else { 0384 //! when plasma theme uses the kde colors 0385 //! we track when kde color scheme is changing 0386 QString kdeSettingsFile = Latte::configPath() + "/kdeglobals"; 0387 0388 KDirWatch::self()->addFile(kdeSettingsFile); 0389 0390 m_kdeConnections[0] = connect(KDirWatch::self(), &KDirWatch::dirty, this, [ &, kdeSettingsFile](const QString & path) { 0391 if (path == kdeSettingsFile) { 0392 this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); 0393 } 0394 }); 0395 0396 m_kdeConnections[1] = connect(KDirWatch::self(), &KDirWatch::created, this, [ &, kdeSettingsFile](const QString & path) { 0397 if (path == kdeSettingsFile) { 0398 this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); 0399 } 0400 }); 0401 0402 setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); 0403 } 0404 } 0405 0406 void Theme::loadThemeLightness() 0407 { 0408 float textColorLum = Latte::colorLumina(m_defaultScheme->textColor()); 0409 float backColorLum = Latte::colorLumina(m_defaultScheme->backgroundColor()); 0410 0411 if (backColorLum > textColorLum) { 0412 m_isLightTheme = true; 0413 } else { 0414 m_isLightTheme = false; 0415 } 0416 0417 if (m_isLightTheme) { 0418 qDebug() << "Plasma theme is light..."; 0419 } else { 0420 qDebug() << "Plasma theme is dark..."; 0421 } 0422 } 0423 0424 const CornerRegions &Theme::cornersMask(const int &radius) 0425 { 0426 if (m_cornerRegions.contains(radius)) { 0427 return m_cornerRegions[radius]; 0428 } 0429 0430 qDebug() << radius; 0431 CornerRegions corners; 0432 0433 int axis = (2 * radius) + 2; 0434 QImage cornerimage(axis, axis, QImage::Format_ARGB32); 0435 QPainter painter(&cornerimage); 0436 //!does not provide valid masks ? 0437 painter.setRenderHints(QPainter::Antialiasing); 0438 0439 QPen pen(Qt::black); 0440 pen.setStyle(Qt::SolidLine); 0441 pen.setWidth(1); 0442 painter.setPen(pen); 0443 0444 QRect rectArea(0,0,axis,axis); 0445 painter.fillRect(rectArea, Qt::white); 0446 painter.drawRoundedRect(rectArea, axis, axis); 0447 0448 QRegion topleft; 0449 for(int y=0; y<radius; ++y) { 0450 QRgb *line = (QRgb *)cornerimage.scanLine(y); 0451 0452 QString bits; 0453 int width{0}; 0454 for(int x=0; x<radius; ++x) { 0455 QRgb point = line[x]; 0456 0457 if (QColor(point) != Qt::white) { 0458 bits = bits + "1 "; 0459 width = qMax(0, x); 0460 break; 0461 } else { 0462 bits = bits + "0 "; 0463 } 0464 } 0465 0466 if (width>0) { 0467 topleft += QRect(0, y, width, 1); 0468 } 0469 0470 qDebug()<< " " << bits; 0471 } 0472 corners.topLeft = topleft; 0473 0474 QTransform transform; 0475 transform.rotate(90); 0476 corners.topRight = transform.map(corners.topLeft); 0477 corners.topRight.translate(corners.topLeft.boundingRect().width(), 0); 0478 0479 corners.bottomRight = transform.map(corners.topRight); 0480 corners.bottomRight.translate(corners.topLeft.boundingRect().width(), 0); 0481 0482 corners.bottomLeft = transform.map(corners.bottomRight); 0483 corners.bottomLeft.translate(corners.topLeft.boundingRect().width(), 0); 0484 0485 //qDebug() << " reg top;: " << corners.topLeft; 0486 //qDebug() << " reg topr: " << corners.topRight; 0487 //qDebug() << " reg bottomr: " << corners.bottomRight; 0488 //qDebug() << " reg bottoml: " << corners.bottomLeft; 0489 0490 m_cornerRegions[radius] = corners; 0491 return m_cornerRegions[radius]; 0492 } 0493 0494 void Theme::updateMarginsAreaValues() 0495 { 0496 m_marginsAreaTop = 0; 0497 m_marginsAreaLeft = 0; 0498 m_marginsAreaBottom = 0; 0499 m_marginsAreaRight = 0; 0500 0501 Plasma::Svg *svg = new Plasma::Svg(this); 0502 svg->setImagePath(QStringLiteral("widgets/panel-background")); 0503 0504 bool hasThickSeparatorMargins = svg->hasElement("thick-center"); 0505 0506 if (hasThickSeparatorMargins) { 0507 int topMargin = svg->hasElement("hint-top-margin") ? svg->elementSize("hint-top-margin").height() : 0; 0508 int leftMargin = svg->hasElement("hint-left-margin") ? svg->elementSize("hint-left-margin").width() : 0; 0509 int bottomMargin = svg->hasElement("hint-bottom-margin") ? svg->elementSize("hint-bottom-margin").height() : 0; 0510 int rightMargin = svg->hasElement("hint-right-margin") ? svg->elementSize("hint-right-margin").width() : 0; 0511 0512 int thickTopMargin = svg->hasElement("thick-hint-top-margin") ? svg->elementSize("thick-hint-top-margin").height() : 0; 0513 int thickLeftMargin = svg->hasElement("thick-hint-left-margin") ? svg->elementSize("thick-hint-left-margin").width() : 0; 0514 int thickBottomMargin = svg->hasElement("thick-hint-bottom-margin") ? svg->elementSize("thick-hint-bottom-margin").height() : 0; 0515 int thickRightMargin = svg->hasElement("thick-hint-right-margin") ? svg->elementSize("thick-hint-right-margin").width() : 0; 0516 0517 m_marginsAreaTop = qMax(0, thickTopMargin - topMargin); 0518 m_marginsAreaLeft = qMax(0, thickLeftMargin - leftMargin); 0519 m_marginsAreaBottom = qMax(0, thickBottomMargin - bottomMargin); 0520 m_marginsAreaRight = qMax(0, thickRightMargin - rightMargin); 0521 } 0522 0523 qDebug() << "PLASMA THEME MARGINS AREA ::" << 0524 m_marginsAreaTop << m_marginsAreaLeft << 0525 m_marginsAreaBottom << m_marginsAreaRight; 0526 0527 svg->deleteLater(); 0528 0529 emit marginsAreaChanged(); 0530 } 0531 0532 void Theme::loadConfig() 0533 { 0534 setOutlineWidth(m_themeGroup.readEntry("outlineWidth", 1)); 0535 } 0536 0537 void Theme::saveConfig() 0538 { 0539 m_themeGroup.writeEntry("outlineWidth", m_outlineWidth); 0540 } 0541 0542 void Theme::qmlRegisterTypes() 0543 { 0544 qmlRegisterAnonymousType<Latte::PlasmaExtended::Theme>("latte-dock", 1); 0545 qmlRegisterAnonymousType<Latte::PlasmaExtended::PanelBackground>("latte-dock", 1); 0546 } 0547 0548 } 0549 }