File indexing completed on 2024-04-21 05:30:53

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 }