1 /*
2 SPDX-FileCopyrightText: 2018 Michail Vourlakos <mvourlakos@gmail.com>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "theme.h"
7
8 // local
9 #include "lattecorona.h"
10 #include "panelbackground.h"
11 #include "../../layouts/importer.h"
12 #include "../../view/panelshadows_p.h"
13 #include "../../wm/schemecolors.h"
14 #include "../../tools/commontools.h"
15
16 // Qt
17 #include <QDebug>
18 #include <QDir>
19 #include <QPainter>
20
21 // KDE
22 #include <KDirWatch>
23 #include <KConfigGroup>
24 #include <KSharedConfig>
25
26 // X11
27 #include <KWindowSystem>
28
29 #define DEFAULTCOLORSCHEME "default.colors"
30 #define REVERSEDCOLORSCHEME "reversed.colors"
31
32 namespace Latte {
33 namespace PlasmaExtended {
34
Theme(KSharedConfig::Ptr config,QObject * parent)35 Theme::Theme(KSharedConfig::Ptr config, QObject *parent) :
36 QObject(parent),
37 m_themeGroup(KConfigGroup(config, QStringLiteral("PlasmaThemeExtended"))),
38 m_backgroundTopEdge(new PanelBackground(Plasma::Types::TopEdge, this)),
39 m_backgroundLeftEdge(new PanelBackground(Plasma::Types::LeftEdge, this)),
40 m_backgroundBottomEdge(new PanelBackground(Plasma::Types::BottomEdge, this)),
41 m_backgroundRightEdge(new PanelBackground(Plasma::Types::RightEdge, this))
42 {
43 qmlRegisterTypes();
44
45 m_corona = qobject_cast<Latte::Corona *>(parent);
46
47 //! compositing tracking
48 if (KWindowSystem::isPlatformWayland()) {
49 //! TODO: Wayland compositing active
50 m_compositing = true;
51 } else {
52 connect(KWindowSystem::self(), &KWindowSystem::compositingChanged
53 , this, [&](bool enabled) {
54 if (m_compositing == enabled)
55 return;
56
57 m_compositing = enabled;
58 emit compositingChanged();
59 });
60
61 m_compositing = KWindowSystem::compositingActive();
62 }
63 //!
64
65 loadConfig();
66
67 connect(this, &Theme::compositingChanged, this, &Theme::updateBackgrounds);
68 connect(this, &Theme::outlineWidthChanged, this, &Theme::saveConfig);
69
70 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::load);
71 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::themeChanged);
72 }
73
load()74 void Theme::load()
75 {
76 loadThemePaths();
77 updateBackgrounds();
78 updateMarginsAreaValues();
79 }
80
~Theme()81 Theme::~Theme()
82 {
83 saveConfig();
84
85 m_defaultScheme->deleteLater();
86 m_reversedScheme->deleteLater();
87 }
88
hasShadow() const89 bool Theme::hasShadow() const
90 {
91 return m_hasShadow;
92 }
93
isLightTheme() const94 bool Theme::isLightTheme() const
95 {
96 return m_isLightTheme;
97 }
98
isDarkTheme() const99 bool Theme::isDarkTheme() const
100 {
101 return !m_isLightTheme;
102 }
103
outlineWidth() const104 int Theme::outlineWidth() const
105 {
106 return m_outlineWidth;
107 }
108
setOutlineWidth(int width)109 void Theme::setOutlineWidth(int width)
110 {
111 if (m_outlineWidth == width) {
112 return;
113 }
114
115 m_outlineWidth = width;
116 emit outlineWidthChanged();
117 }
118
marginsAreaTop() const119 int Theme::marginsAreaTop() const
120 {
121 return m_marginsAreaTop;
122 }
123
marginsAreaLeft() const124 int Theme::marginsAreaLeft() const
125 {
126 return m_marginsAreaLeft;
127 }
128
marginsAreaBottom() const129 int Theme::marginsAreaBottom() const
130 {
131 return m_marginsAreaBottom;
132 }
133
marginsAreaRight() const134 int Theme::marginsAreaRight() const
135 {
136 return m_marginsAreaRight;
137 }
138
139
backgroundTopEdge() const140 PanelBackground *Theme::backgroundTopEdge() const
141 {
142 return m_backgroundTopEdge;
143 }
144
backgroundLeftEdge() const145 PanelBackground *Theme::backgroundLeftEdge() const
146 {
147 return m_backgroundLeftEdge;
148 }
149
backgroundBottomEdge() const150 PanelBackground *Theme::backgroundBottomEdge() const
151 {
152 return m_backgroundBottomEdge;
153 }
154
backgroundRightEdge() const155 PanelBackground *Theme::backgroundRightEdge() const
156 {
157 return m_backgroundRightEdge;
158 }
159
defaultTheme() const160 WindowSystem::SchemeColors *Theme::defaultTheme() const
161 {
162 return m_defaultScheme;
163 }
164
lightTheme() const165 WindowSystem::SchemeColors *Theme::lightTheme() const
166 {
167 return m_isLightTheme ? m_defaultScheme : m_reversedScheme;
168 }
169
darkTheme() const170 WindowSystem::SchemeColors *Theme::darkTheme() const
171 {
172 return !m_isLightTheme ? m_defaultScheme : m_reversedScheme;
173 }
174
175
setOriginalSchemeFile(const QString & file)176 void Theme::setOriginalSchemeFile(const QString &file)
177 {
178 if (m_originalSchemePath == file) {
179 return;
180 }
181
182 m_originalSchemePath = file;
183
184 qDebug() << "plasma theme original colors ::: " << m_originalSchemePath;
185
186 updateDefaultScheme();
187 updateReversedScheme();
188
189 loadThemeLightness();
190
191 emit themeChanged();
192 }
193
194 //! WM records need to be updated based on the colors that
195 //! plasma will use in order to be consistent. Such an example
196 //! are the Breeze color schemes that have different values for
197 //! WM and the plasma theme records
updateDefaultScheme()198 void Theme::updateDefaultScheme()
199 {
200 QString defaultFilePath = m_extendedThemeDir.path() + "/" + DEFAULTCOLORSCHEME;
201 if (QFileInfo(defaultFilePath).exists()) {
202 QFile(defaultFilePath).remove();
203 }
204
205 QFile(m_originalSchemePath).copy(defaultFilePath);
206 m_defaultSchemePath = defaultFilePath;
207
208 updateDefaultSchemeValues();
209
210 if (m_defaultScheme) {
211 disconnect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness);
212 m_defaultScheme->deleteLater();
213 }
214
215 m_defaultScheme = new WindowSystem::SchemeColors(this, m_defaultSchemePath, true);
216 connect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness);
217
218 qDebug() << "plasma theme default colors ::: " << m_defaultSchemePath;
219 }
220
updateDefaultSchemeValues()221 void Theme::updateDefaultSchemeValues()
222 {
223 //! update WM values based on original scheme
224 KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath);
225 KSharedConfigPtr defaultPtr = KSharedConfig::openConfig(m_defaultSchemePath);
226
227 if (originalPtr && defaultPtr) {
228 KConfigGroup normalWindowGroup(originalPtr, "Colors:Window");
229 KConfigGroup defaultWMGroup(defaultPtr, "WM");
230
231 defaultWMGroup.writeEntry("activeBackground", normalWindowGroup.readEntry("BackgroundNormal", QColor()));
232 defaultWMGroup.writeEntry("activeForeground", normalWindowGroup.readEntry("ForegroundNormal", QColor()));
233
234 defaultWMGroup.sync();
235 }
236 }
237
updateReversedScheme()238 void Theme::updateReversedScheme()
239 {
240 QString reversedFilePath = m_extendedThemeDir.path() + "/" + REVERSEDCOLORSCHEME;
241
242 if (QFileInfo(reversedFilePath).exists()) {
243 QFile(reversedFilePath).remove();
244 }
245
246 QFile(m_originalSchemePath).copy(reversedFilePath);
247 m_reversedSchemePath = reversedFilePath;
248
249 updateReversedSchemeValues();
250
251 if (m_reversedScheme) {
252 m_reversedScheme->deleteLater();
253 }
254
255 m_reversedScheme = new WindowSystem::SchemeColors(this, m_reversedSchemePath, true);
256
257 qDebug() << "plasma theme reversed colors ::: " << m_reversedSchemePath;
258 }
259
updateReversedSchemeValues()260 void Theme::updateReversedSchemeValues()
261 {
262 //! reverse values based on original scheme
263 KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath);
264 KSharedConfigPtr reversedPtr = KSharedConfig::openConfig(m_reversedSchemePath);
265
266 if (originalPtr && reversedPtr) {
267 for (const auto &groupName : reversedPtr->groupList()) {
268 if (groupName != "Colors:Button" && groupName != "Colors:Selection") {
269 KConfigGroup reversedGroup(reversedPtr, groupName);
270
271 if (reversedGroup.keyList().contains("BackgroundNormal")
272 && reversedGroup.keyList().contains("ForegroundNormal")) {
273 //! reverse usual text/background values
274 KConfigGroup originalGroup(originalPtr, groupName);
275
276 reversedGroup.writeEntry("BackgroundNormal", originalGroup.readEntry("ForegroundNormal", QColor()));
277 reversedGroup.writeEntry("ForegroundNormal", originalGroup.readEntry("BackgroundNormal", QColor()));
278
279 reversedGroup.sync();
280 }
281 }
282 }
283
284 //! update WM group
285 KConfigGroup reversedWMGroup(reversedPtr, "WM");
286 KConfigGroup normalWindowGroup(originalPtr, "Colors:Window");
287
288 if (reversedWMGroup.keyList().contains("activeBackground")
289 && reversedWMGroup.keyList().contains("activeForeground")
290 && reversedWMGroup.keyList().contains("inactiveBackground")
291 && reversedWMGroup.keyList().contains("inactiveForeground")) {
292 //! reverse usual wm titlebar values
293 KConfigGroup originalGroup(originalPtr, "WM");
294 reversedWMGroup.writeEntry("activeBackground", normalWindowGroup.readEntry("ForegroundNormal", QColor()));
295 reversedWMGroup.writeEntry("activeForeground", normalWindowGroup.readEntry("BackgroundNormal", QColor()));
296 reversedWMGroup.writeEntry("inactiveBackground", originalGroup.readEntry("inactiveForeground", QColor()));
297 reversedWMGroup.writeEntry("inactiveForeground", originalGroup.readEntry("inactiveBackground", QColor()));
298 reversedWMGroup.sync();
299 }
300
301 if (reversedWMGroup.keyList().contains("activeBlend")
302 && reversedWMGroup.keyList().contains("inactiveBlend")) {
303 KConfigGroup originalGroup(originalPtr, "WM");
304 reversedWMGroup.writeEntry("activeBlend", originalGroup.readEntry("inactiveBlend", QColor()));
305 reversedWMGroup.writeEntry("inactiveBlend", originalGroup.readEntry("activeBlend", QColor()));
306 reversedWMGroup.sync();
307 }
308
309 //! update scheme name
310 QString originalSchemeName = WindowSystem::SchemeColors::schemeName(m_originalSchemePath);
311 KConfigGroup generalGroup(reversedPtr, "General");
312 generalGroup.writeEntry("Name", originalSchemeName + "_reversed");
313 generalGroup.sync();
314 }
315 }
316
updateBackgrounds()317 void Theme::updateBackgrounds()
318 {
319 updateHasShadow();
320
321 m_backgroundTopEdge->update();
322 m_backgroundLeftEdge->update();
323 m_backgroundBottomEdge->update();
324 m_backgroundRightEdge->update();
325 }
326
updateHasShadow()327 void Theme::updateHasShadow()
328 {
329 Plasma::Svg *svg = new Plasma::Svg(this);
330 svg->setImagePath(QStringLiteral("widgets/panel-background"));
331 svg->resize();
332
333 QString cornerId = "shadow-topleft";
334 QImage corner = svg->image(svg->elementSize(cornerId), cornerId);
335
336 int fullTransparentPixels = 0;
337
338 for(int c=0; c<corner.width(); ++c) {
339 for(int r=0; r<corner.height(); ++r) {
340 QRgb *line = (QRgb *)corner.scanLine(r);
341 QRgb point = line[c];
342
343 if (qAlpha(point) == 0) {
344 fullTransparentPixels++;
345 }
346 }
347 }
348
349 int pixels = (corner.width() * corner.height());
350
351 m_hasShadow = (fullTransparentPixels != pixels );
352 emit hasShadowChanged();
353
354 qDebug() << " PLASMA THEME TOPLEFT SHADOW :: pixels : " << pixels << " transparent pixels" << fullTransparentPixels << " | HAS SHADOWS :" << m_hasShadow;
355
356 svg->deleteLater();
357 }
358
loadThemePaths()359 void Theme::loadThemePaths()
360 {
361 m_themePath = Layouts::Importer::standardPath("plasma/desktoptheme/" + m_theme.themeName());
362
363 if (QDir(m_themePath+"/widgets").exists()) {
364 m_themeWidgetsPath = m_themePath + "/widgets";
365 } else {
366 m_themeWidgetsPath = Layouts::Importer::standardPath("plasma/desktoptheme/default/widgets");
367 }
368
369 qDebug() << "current plasma theme ::: " << m_theme.themeName();
370 qDebug() << "theme path ::: " << m_themePath;
371 qDebug() << "theme widgets path ::: " << m_themeWidgetsPath;
372
373 //! clear kde connections
374 for (auto &c : m_kdeConnections) {
375 disconnect(c);
376 }
377
378 //! assign color schemes
379 QString themeColorScheme = m_themePath + "/colors";
380
381 if (QFileInfo(themeColorScheme).exists()) {
382 setOriginalSchemeFile(themeColorScheme);
383 } else {
384 //! when plasma theme uses the kde colors
385 //! we track when kde color scheme is changing
386 QString kdeSettingsFile = Latte::configPath() + "/kdeglobals";
387
388 KDirWatch::self()->addFile(kdeSettingsFile);
389
390 m_kdeConnections[0] = connect(KDirWatch::self(), &KDirWatch::dirty, this, [ &, kdeSettingsFile](const QString & path) {
391 if (path == kdeSettingsFile) {
392 this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals"));
393 }
394 });
395
396 m_kdeConnections[1] = connect(KDirWatch::self(), &KDirWatch::created, this, [ &, kdeSettingsFile](const QString & path) {
397 if (path == kdeSettingsFile) {
398 this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals"));
399 }
400 });
401
402 setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals"));
403 }
404 }
405
loadThemeLightness()406 void Theme::loadThemeLightness()
407 {
408 float textColorLum = Latte::colorLumina(m_defaultScheme->textColor());
409 float backColorLum = Latte::colorLumina(m_defaultScheme->backgroundColor());
410
411 if (backColorLum > textColorLum) {
412 m_isLightTheme = true;
413 } else {
414 m_isLightTheme = false;
415 }
416
417 if (m_isLightTheme) {
418 qDebug() << "Plasma theme is light...";
419 } else {
420 qDebug() << "Plasma theme is dark...";
421 }
422 }
423
cornersMask(const int & radius)424 const CornerRegions &Theme::cornersMask(const int &radius)
425 {
426 if (m_cornerRegions.contains(radius)) {
427 return m_cornerRegions[radius];
428 }
429
430 qDebug() << radius;
431 CornerRegions corners;
432
433 int axis = (2 * radius) + 2;
434 QImage cornerimage(axis, axis, QImage::Format_ARGB32);
435 QPainter painter(&cornerimage);
436 //!does not provide valid masks
437 //painter.setRenderHints(QPainter::Antialiasing);
438
439 QPen pen(Qt::black);
440 pen.setStyle(Qt::SolidLine);
441 pen.setWidth(1);
442 painter.setPen(pen);
443
444 QRect rectArea(0,0,axis,axis);
445 painter.fillRect(rectArea, Qt::white);
446 painter.drawRoundedRect(rectArea, axis, axis);
447
448 QRegion topleft;
449 for(int y=0; y<radius; ++y) {
450 QRgb *line = (QRgb *)cornerimage.scanLine(y);
451
452 QString bits;
453 int width{0};
454 for(int x=0; x<radius; ++x) {
455 QRgb point = line[x];
456
457 if (QColor(point) == Qt::black) {
458 bits = bits + "1 ";
459 width = qMax(0, x);
460 break;
461 } else {
462 bits = bits + "0 ";
463 }
464 }
465
466 if (width>0) {
467 topleft += QRect(0, y, width, 1);
468 }
469
470 qDebug()<< " " << bits;
471 }
472 corners.topLeft = topleft;
473
474 QTransform transform;
475 transform.rotate(90);
476 corners.topRight = transform.map(corners.topLeft);
477 corners.topRight.translate(corners.topLeft.boundingRect().width(), 0);
478
479 corners.bottomRight = transform.map(corners.topRight);
480 corners.bottomRight.translate(corners.topLeft.boundingRect().width(), 0);
481
482 corners.bottomLeft = transform.map(corners.bottomRight);
483 corners.bottomLeft.translate(corners.topLeft.boundingRect().width(), 0);
484
485 //qDebug() << " reg top;: " << corners.topLeft;
486 //qDebug() << " reg topr: " << corners.topRight;
487 //qDebug() << " reg bottomr: " << corners.bottomRight;
488 //qDebug() << " reg bottoml: " << corners.bottomLeft;
489
490 m_cornerRegions[radius] = corners;
491 return m_cornerRegions[radius];
492 }
493
updateMarginsAreaValues()494 void Theme::updateMarginsAreaValues()
495 {
496 m_marginsAreaTop = 0;
497 m_marginsAreaLeft = 0;
498 m_marginsAreaBottom = 0;
499 m_marginsAreaRight = 0;
500
501 Plasma::Svg *svg = new Plasma::Svg(this);
502 svg->setImagePath(QStringLiteral("widgets/panel-background"));
503
504 bool hasThickSeparatorMargins = svg->hasElement("thick-center");
505
506 if (hasThickSeparatorMargins) {
507 int topMargin = svg->hasElement("hint-top-margin") ? svg->elementSize("hint-top-margin").height() : 0;
508 int leftMargin = svg->hasElement("hint-left-margin") ? svg->elementSize("hint-left-margin").width() : 0;
509 int bottomMargin = svg->hasElement("hint-bottom-margin") ? svg->elementSize("hint-bottom-margin").height() : 0;
510 int rightMargin = svg->hasElement("hint-right-margin") ? svg->elementSize("hint-right-margin").width() : 0;
511
512 int thickTopMargin = svg->hasElement("thick-hint-top-margin") ? svg->elementSize("thick-hint-top-margin").height() : 0;
513 int thickLeftMargin = svg->hasElement("thick-hint-left-margin") ? svg->elementSize("thick-hint-left-margin").width() : 0;
514 int thickBottomMargin = svg->hasElement("thick-hint-bottom-margin") ? svg->elementSize("thick-hint-bottom-margin").height() : 0;
515 int thickRightMargin = svg->hasElement("thick-hint-right-margin") ? svg->elementSize("thick-hint-right-margin").width() : 0;
516
517 m_marginsAreaTop = qMax(0, thickTopMargin - topMargin);
518 m_marginsAreaLeft = qMax(0, thickLeftMargin - leftMargin);
519 m_marginsAreaBottom = qMax(0, thickBottomMargin - bottomMargin);
520 m_marginsAreaRight = qMax(0, thickRightMargin - rightMargin);
521 }
522
523 qDebug() << "PLASMA THEME MARGINS AREA ::" <<
524 m_marginsAreaTop << m_marginsAreaLeft <<
525 m_marginsAreaBottom << m_marginsAreaRight;
526
527 svg->deleteLater();
528
529 emit marginsAreaChanged();
530 }
531
loadConfig()532 void Theme::loadConfig()
533 {
534 setOutlineWidth(m_themeGroup.readEntry("outlineWidth", 1));
535 }
536
saveConfig()537 void Theme::saveConfig()
538 {
539 m_themeGroup.writeEntry("outlineWidth", m_outlineWidth);
540 }
541
qmlRegisterTypes()542 void Theme::qmlRegisterTypes()
543 {
544 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
545 qmlRegisterType<Latte::PlasmaExtended::Theme>();
546 qmlRegisterType<Latte::PlasmaExtended::PanelBackground>();
547 #else
548 qmlRegisterAnonymousType<Latte::PlasmaExtended::Theme>("latte-dock", 1);
549 qmlRegisterAnonymousType<Latte::PlasmaExtended::PanelBackground>("latte-dock", 1);
550 #endif
551 }
552
553 }
554 }
555