File indexing completed on 2024-04-21 16:17:10
0001 /* 0002 * Copyright 2018 Michail Vourlakos <mvourlakos@gmail.com> 0003 * 0004 * This file is part of Latte-Dock 0005 * 0006 * Latte-Dock is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU General Public License as 0008 * published by the Free Software Foundation; either version 2 of 0009 * the License, or (at your option) any later version. 0010 * 0011 * Latte-Dock is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License 0017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0018 * 0019 */ 0020 0021 #include "theme.h" 0022 0023 // local 0024 #include "lattecorona.h" 0025 #include "../../layouts/importer.h" 0026 #include "../../view/panelshadows_p.h" 0027 #include "../../wm/schemecolors.h" 0028 #include "../../../liblatte2/commontools.h" 0029 0030 // Qt 0031 #include <QDebug> 0032 #include <QDir> 0033 #include <QProcess> 0034 0035 // KDE 0036 #include <KDirWatch> 0037 #include <KConfigGroup> 0038 #include <KSharedConfig> 0039 0040 // X11 0041 #include <KWindowSystem> 0042 0043 #define DEFAULTCOLORSCHEME "default.colors" 0044 #define REVERSEDCOLORSCHEME "reversed.colors" 0045 0046 namespace Latte { 0047 namespace PlasmaExtended { 0048 0049 Theme::Theme(KSharedConfig::Ptr config, QObject *parent) : 0050 QObject(parent), 0051 m_themeGroup(KConfigGroup(config, QStringLiteral("PlasmaThemeExtended"))) 0052 { 0053 m_corona = qobject_cast<Latte::Corona *>(parent); 0054 0055 //! compositing tracking 0056 if (KWindowSystem::isPlatformWayland()) { 0057 //! TODO: Wayland compositing active 0058 m_compositing = true; 0059 } else { 0060 connect(KWindowSystem::self(), &KWindowSystem::compositingChanged 0061 , this, [&](bool enabled) { 0062 if (m_compositing == enabled) 0063 return; 0064 0065 m_compositing = enabled; 0066 emit compositingChanged(); 0067 }); 0068 0069 m_compositing = KWindowSystem::compositingActive(); 0070 } 0071 //! 0072 0073 loadConfig(); 0074 0075 connect(this, &Theme::compositingChanged, this, &Theme::roundnessChanged); 0076 connect(this, &Theme::outlineWidthChanged, this, &Theme::saveConfig); 0077 0078 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::hasShadowChanged); 0079 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::load); 0080 connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::themeChanged); 0081 } 0082 0083 void Theme::load() 0084 { 0085 loadThemePaths(); 0086 loadRoundness(); 0087 } 0088 0089 Theme::~Theme() 0090 { 0091 saveConfig(); 0092 0093 m_defaultScheme->deleteLater(); 0094 m_reversedScheme->deleteLater(); 0095 } 0096 0097 bool Theme::hasShadow() const 0098 { 0099 return PanelShadows::self()->hasShadows(); 0100 } 0101 0102 bool Theme::isLightTheme() const 0103 { 0104 return m_isLightTheme; 0105 } 0106 0107 bool Theme::isDarkTheme() const 0108 { 0109 return !m_isLightTheme; 0110 } 0111 0112 int Theme::bottomEdgeRoundness() const 0113 { 0114 return m_bottomEdgeRoundness; 0115 } 0116 0117 int Theme::leftEdgeRoundness() const 0118 { 0119 return m_leftEdgeRoundness; 0120 } 0121 0122 int Theme::topEdgeRoundness() const 0123 { 0124 return m_topEdgeRoundness; 0125 } 0126 0127 int Theme::rightEdgeRoundness() const 0128 { 0129 return m_rightEdgeRoundness; 0130 } 0131 0132 int Theme::outlineWidth() const 0133 { 0134 return m_outlineWidth; 0135 } 0136 0137 void Theme::setOutlineWidth(int width) 0138 { 0139 if (m_outlineWidth == width) { 0140 return; 0141 } 0142 0143 m_outlineWidth = width; 0144 emit outlineWidthChanged(); 0145 } 0146 0147 float Theme::bottomEdgeMaxOpacity() const 0148 { 0149 return m_bottomEdgeMaxOpacity; 0150 } 0151 0152 float Theme::leftEdgeMaxOpacity() const 0153 { 0154 return m_leftEdgeMaxOpacity; 0155 } 0156 0157 float Theme::topEdgeMaxOpacity() const 0158 { 0159 return m_topEdgeMaxOpacity; 0160 } 0161 0162 float Theme::rightEdgeMaxOpacity() const 0163 { 0164 return m_rightEdgeMaxOpacity; 0165 } 0166 0167 WindowSystem::SchemeColors *Theme::defaultTheme() const 0168 { 0169 return m_defaultScheme; 0170 } 0171 0172 WindowSystem::SchemeColors *Theme::lightTheme() const 0173 { 0174 return m_isLightTheme ? m_defaultScheme : m_reversedScheme; 0175 } 0176 0177 WindowSystem::SchemeColors *Theme::darkTheme() const 0178 { 0179 return !m_isLightTheme ? m_defaultScheme : m_reversedScheme; 0180 } 0181 0182 0183 void Theme::setOriginalSchemeFile(const QString &file) 0184 { 0185 if (m_originalSchemePath == file) { 0186 return; 0187 } 0188 0189 m_originalSchemePath = file; 0190 0191 qDebug() << "plasma theme original colors ::: " << m_originalSchemePath; 0192 0193 updateDefaultScheme(); 0194 updateReversedScheme(); 0195 0196 loadThemeLightness(); 0197 0198 emit themeChanged(); 0199 } 0200 0201 //! WM records need to be updated based on the colors that 0202 //! plasma will use in order to be consistent. Such an example 0203 //! are the Breeze color schemes that have different values for 0204 //! WM and the plasma theme records 0205 void Theme::updateDefaultScheme() 0206 { 0207 QString defaultFilePath = m_extendedThemeDir.path() + "/" + DEFAULTCOLORSCHEME; 0208 if (QFileInfo(defaultFilePath).exists()) { 0209 QFile(defaultFilePath).remove(); 0210 } 0211 0212 QFile(m_originalSchemePath).copy(defaultFilePath); 0213 m_defaultSchemePath = defaultFilePath; 0214 0215 updateDefaultSchemeValues(); 0216 0217 if (m_defaultScheme) { 0218 disconnect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness); 0219 m_defaultScheme->deleteLater(); 0220 } 0221 0222 m_defaultScheme = new WindowSystem::SchemeColors(this, m_defaultSchemePath, true); 0223 connect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness); 0224 0225 qDebug() << "plasma theme default colors ::: " << m_defaultSchemePath; 0226 } 0227 0228 void Theme::updateDefaultSchemeValues() 0229 { 0230 //! update WM values based on original scheme 0231 KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath); 0232 KSharedConfigPtr defaultPtr = KSharedConfig::openConfig(m_defaultSchemePath); 0233 0234 if (originalPtr && defaultPtr) { 0235 KConfigGroup normalWindowGroup(originalPtr, "Colors:Window"); 0236 KConfigGroup defaultWMGroup(defaultPtr, "WM"); 0237 0238 defaultWMGroup.writeEntry("activeBackground", normalWindowGroup.readEntry("BackgroundNormal", QColor())); 0239 defaultWMGroup.writeEntry("activeForeground", normalWindowGroup.readEntry("ForegroundNormal", QColor())); 0240 0241 defaultWMGroup.sync(); 0242 } 0243 } 0244 0245 void Theme::updateReversedScheme() 0246 { 0247 QString reversedFilePath = m_extendedThemeDir.path() + "/" + REVERSEDCOLORSCHEME; 0248 0249 if (QFileInfo(reversedFilePath).exists()) { 0250 QFile(reversedFilePath).remove(); 0251 } 0252 0253 QFile(m_originalSchemePath).copy(reversedFilePath); 0254 m_reversedSchemePath = reversedFilePath; 0255 0256 updateReversedSchemeValues(); 0257 0258 if (m_reversedScheme) { 0259 m_reversedScheme->deleteLater(); 0260 } 0261 0262 m_reversedScheme = new WindowSystem::SchemeColors(this, m_reversedSchemePath, true); 0263 0264 qDebug() << "plasma theme reversed colors ::: " << m_reversedSchemePath; 0265 } 0266 0267 void Theme::updateReversedSchemeValues() 0268 { 0269 //! reverse values based on original scheme 0270 KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath); 0271 KSharedConfigPtr reversedPtr = KSharedConfig::openConfig(m_reversedSchemePath); 0272 0273 if (originalPtr && reversedPtr) { 0274 for (const auto &groupName : reversedPtr->groupList()) { 0275 if (groupName != "Colors:Button" && groupName != "Colors:Selection") { 0276 KConfigGroup reversedGroup(reversedPtr, groupName); 0277 0278 if (reversedGroup.keyList().contains("BackgroundNormal") 0279 && reversedGroup.keyList().contains("ForegroundNormal")) { 0280 //! reverse usual text/background values 0281 KConfigGroup originalGroup(originalPtr, groupName); 0282 0283 reversedGroup.writeEntry("BackgroundNormal", originalGroup.readEntry("ForegroundNormal", QColor())); 0284 reversedGroup.writeEntry("ForegroundNormal", originalGroup.readEntry("BackgroundNormal", QColor())); 0285 0286 reversedGroup.sync(); 0287 } 0288 } 0289 } 0290 0291 //! update WM group 0292 KConfigGroup reversedWMGroup(reversedPtr, "WM"); 0293 KConfigGroup normalWindowGroup(originalPtr, "Colors:Window"); 0294 0295 if (reversedWMGroup.keyList().contains("activeBackground") 0296 && reversedWMGroup.keyList().contains("activeForeground") 0297 && reversedWMGroup.keyList().contains("inactiveBackground") 0298 && reversedWMGroup.keyList().contains("inactiveForeground")) { 0299 //! reverse usual wm titlebar values 0300 KConfigGroup originalGroup(originalPtr, "WM"); 0301 reversedWMGroup.writeEntry("activeBackground", normalWindowGroup.readEntry("ForegroundNormal", QColor())); 0302 reversedWMGroup.writeEntry("activeForeground", normalWindowGroup.readEntry("BackgroundNormal", QColor())); 0303 reversedWMGroup.writeEntry("inactiveBackground", originalGroup.readEntry("inactiveForeground", QColor())); 0304 reversedWMGroup.writeEntry("inactiveForeground", originalGroup.readEntry("inactiveBackground", QColor())); 0305 reversedWMGroup.sync(); 0306 } 0307 0308 if (reversedWMGroup.keyList().contains("activeBlend") 0309 && reversedWMGroup.keyList().contains("inactiveBlend")) { 0310 KConfigGroup originalGroup(originalPtr, "WM"); 0311 reversedWMGroup.writeEntry("activeBlend", originalGroup.readEntry("inactiveBlend", QColor())); 0312 reversedWMGroup.writeEntry("inactiveBlend", originalGroup.readEntry("activeBlend", QColor())); 0313 reversedWMGroup.sync(); 0314 } 0315 0316 //! update scheme name 0317 QString originalSchemeName = WindowSystem::SchemeColors::schemeName(m_originalSchemePath); 0318 KConfigGroup generalGroup(reversedPtr, "General"); 0319 generalGroup.writeEntry("Name", originalSchemeName + "_reversed"); 0320 generalGroup.sync(); 0321 } 0322 } 0323 0324 int Theme::roundness(const QImage &svgImage, Plasma::Types::Location edge) 0325 { 0326 int discovRow = (edge == Plasma::Types::TopEdge ? svgImage.height()-1 : 0); 0327 int discovCol = (edge == Plasma::Types::LeftEdge ? svgImage.width()-1 : 0); 0328 0329 int round{0}; 0330 0331 int maxOpacity = qMin(qAlpha(svgImage.pixel(49,0)), 200); 0332 0333 if (edge == Plasma::Types::BottomEdge) { 0334 m_bottomEdgeMaxOpacity = (float)maxOpacity / (float)255; 0335 } else if (edge == Plasma::Types::LeftEdge) { 0336 m_leftEdgeMaxOpacity = (float)maxOpacity / (float)255; 0337 } else if (edge == Plasma::Types::TopEdge) { 0338 m_topEdgeMaxOpacity = (float)maxOpacity / (float)255; 0339 } else if (edge == Plasma::Types::RightEdge) { 0340 m_rightEdgeMaxOpacity = (float)maxOpacity / (float)255; 0341 } 0342 0343 if (edge == Plasma::Types::BottomEdge || edge == Plasma::Types::RightEdge || edge == Plasma::Types::TopEdge) { 0344 //! TOPLEFT corner 0345 //! first LEFT pixel found 0346 QRgb *line = (QRgb *)svgImage.scanLine(discovRow); 0347 0348 for (int col=0; col<50; ++col) { 0349 QRgb pixelData = line[col]; 0350 0351 if (qAlpha(pixelData) < maxOpacity) { 0352 discovCol++; 0353 round++; 0354 } else { 0355 break; 0356 } 0357 } 0358 } else if (edge == Plasma::Types::LeftEdge) { 0359 //! it should be TOPRIGHT corner in that case 0360 //! first RIGHT pixel found 0361 QRgb *line = (QRgb *)svgImage.scanLine(discovRow); 0362 for (int col=99; col>50; --col) { 0363 QRgb pixelData = line[col]; 0364 0365 if (qAlpha(pixelData) < maxOpacity) { 0366 discovCol--; 0367 round++; 0368 } else { 0369 break; 0370 } 0371 } 0372 } 0373 0374 //! this needs investigation (the x2) I don't know if it is really needed 0375 //! but it gives me the impression that returns better results 0376 return round; ///**2*/; 0377 } 0378 0379 void Theme::loadCompositingRoundness() 0380 { 0381 Plasma::FrameSvg *svg = new Plasma::FrameSvg(this); 0382 svg->setImagePath(QStringLiteral("widgets/panel-background")); 0383 svg->setEnabledBorders(Plasma::FrameSvg::AllBorders); 0384 svg->resizeFrame(QSize(100,100)); 0385 0386 //! New approach 0387 QPixmap pxm = svg->framePixmap(); 0388 0389 //! bottom roundness 0390 if (svg->hasElementPrefix("south")) { 0391 svg->setElementPrefix("south"); 0392 pxm = svg->framePixmap(); 0393 } else { 0394 svg->setElementPrefix(""); 0395 pxm = svg->framePixmap(); 0396 } 0397 m_bottomEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::BottomEdge); 0398 0399 //! left roundness 0400 if (svg->hasElementPrefix("west")) { 0401 svg->setElementPrefix("west"); 0402 pxm = svg->framePixmap(); 0403 } else { 0404 svg->setElementPrefix(""); 0405 pxm = svg->framePixmap(); 0406 } 0407 m_leftEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::LeftEdge); 0408 0409 //! top roundness 0410 if (svg->hasElementPrefix("north")) { 0411 svg->setElementPrefix("north"); 0412 pxm = svg->framePixmap(); 0413 } else { 0414 svg->setElementPrefix(""); 0415 pxm = svg->framePixmap(); 0416 } 0417 m_topEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::TopEdge); 0418 0419 //! right roundness 0420 if (svg->hasElementPrefix("east")) { 0421 svg->setElementPrefix("east"); 0422 pxm = svg->framePixmap(); 0423 } else { 0424 svg->setElementPrefix(""); 0425 pxm = svg->framePixmap(); 0426 } 0427 m_rightEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::RightEdge); 0428 0429 /* qDebug() << " COMPOSITING MASK ::: " << svg->mask(); 0430 qDebug() << " COMPOSITING MASK BOUNDING RECT ::: " << svg->mask().boundingRect();*/ 0431 qDebug() << " COMPOSITING ROUNDNESS ::: " << m_bottomEdgeRoundness << " _ " << m_leftEdgeRoundness << " _ " << m_topEdgeRoundness << " _ " << m_rightEdgeRoundness; 0432 0433 svg->deleteLater(); 0434 } 0435 0436 void Theme::loadRoundness() 0437 { 0438 loadCompositingRoundness(); 0439 0440 emit maxOpacityChanged(); 0441 emit roundnessChanged(); 0442 } 0443 0444 void Theme::loadThemePaths() 0445 { 0446 m_themePath = Layouts::Importer::standardPath("plasma/desktoptheme/" + m_theme.themeName()); 0447 0448 if (QDir(m_themePath+"/widgets").exists()) { 0449 m_themeWidgetsPath = m_themePath + "/widgets"; 0450 } else { 0451 m_themeWidgetsPath = Layouts::Importer::standardPath("plasma/desktoptheme/default/widgets"); 0452 } 0453 0454 qDebug() << "current plasma theme ::: " << m_theme.themeName(); 0455 qDebug() << "theme path ::: " << m_themePath; 0456 qDebug() << "theme widgets path ::: " << m_themeWidgetsPath; 0457 0458 //! clear kde connections 0459 for (auto &c : m_kdeConnections) { 0460 disconnect(c); 0461 } 0462 0463 //! assign color schemes 0464 QString themeColorScheme = m_themePath + "/colors"; 0465 0466 if (QFileInfo(themeColorScheme).exists()) { 0467 setOriginalSchemeFile(themeColorScheme); 0468 } else { 0469 //! when plasma theme uses the kde colors 0470 //! we track when kde color scheme is changing 0471 QString kdeSettingsFile = QDir::homePath() + "/.config/kdeglobals"; 0472 0473 KDirWatch::self()->addFile(kdeSettingsFile); 0474 0475 m_kdeConnections[0] = connect(KDirWatch::self(), &KDirWatch::dirty, this, [ &, kdeSettingsFile](const QString & path) { 0476 if (path == kdeSettingsFile) { 0477 this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); 0478 } 0479 }); 0480 0481 m_kdeConnections[1] = connect(KDirWatch::self(), &KDirWatch::created, this, [ &, kdeSettingsFile](const QString & path) { 0482 if (path == kdeSettingsFile) { 0483 this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); 0484 } 0485 }); 0486 0487 setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); 0488 } 0489 0490 //! this is probably not needed at all in order to provide full transparency for all 0491 //! plasma themes, so we disable it in order to confirm from user testing 0492 //! that it is not needed at all 0493 //parseThemeSvgFiles(); 0494 } 0495 0496 void Theme::parseThemeSvgFiles() 0497 { 0498 QString origBackgroundSvgFile; 0499 QString curBackgroundSvgFile = m_extendedThemeDir.path()+"/widgets/panel-background.svg"; 0500 0501 if (QFileInfo(curBackgroundSvgFile).exists()) { 0502 QDir(m_extendedThemeDir.path()+"/widgets").remove("panel-background.svg"); 0503 } 0504 0505 if (!QDir(m_extendedThemeDir.path()+"/widgets").exists()) { 0506 QDir(m_extendedThemeDir.path()).mkdir("widgets"); 0507 } 0508 0509 if (QFileInfo(m_themeWidgetsPath+"/panel-background.svg").exists()) { 0510 origBackgroundSvgFile = m_themeWidgetsPath+"/panel-background.svg"; 0511 QFile(origBackgroundSvgFile).copy(curBackgroundSvgFile); 0512 } else if (QFileInfo(m_themeWidgetsPath+"/panel-background.svgz").exists()) { 0513 origBackgroundSvgFile = m_themeWidgetsPath+"/panel-background.svgz"; 0514 QString tempBackFile = m_extendedThemeDir.path()+"/widgets/panel-background.svg.gz"; 0515 QFile(origBackgroundSvgFile).copy(tempBackFile); 0516 0517 //! Identify Plasma Desktop version 0518 QProcess process; 0519 process.start("gzip -d " + tempBackFile); 0520 process.waitForFinished(); 0521 QString output(process.readAllStandardOutput()); 0522 0523 qDebug() << "plasma theme, background extraction output ::: " << output; 0524 qDebug() << "plasma theme, original background svg file was decompressed..."; 0525 } 0526 0527 if (QFileInfo(curBackgroundSvgFile).exists()) { 0528 qDebug() << "plasma theme, panel background ::: " << curBackgroundSvgFile; 0529 } else { 0530 qDebug() << "plasma theme, panel background ::: was not found..."; 0531 } 0532 0533 //! Find panel-background transparency 0534 QFile svgFile(curBackgroundSvgFile); 0535 QString styleSvgStr; 0536 0537 if (svgFile.open(QIODevice::ReadOnly)) { 0538 QTextStream in(&svgFile); 0539 bool centerIdFound{false}; 0540 bool styleFound{false}; 0541 0542 while (!in.atEnd() && !styleFound) { 0543 QString line = in.readLine(); 0544 0545 //! each time a rect starts then style can be reset 0546 if (line.contains("<rect")) { 0547 styleSvgStr = ""; 0548 } 0549 0550 //! identify the id "center 0551 if (line.contains("id=\"center\"")) { 0552 centerIdFound = true; 0553 } 0554 0555 //! if valid style for center exists we can break 0556 if (centerIdFound && !styleSvgStr.isEmpty()) { 0557 break; 0558 } 0559 0560 if (centerIdFound && line.contains("style=\"") ) { 0561 styleSvgStr = line; 0562 } 0563 0564 //! when end of "center" you can break 0565 if (centerIdFound && line.contains("/rect>")) { 0566 break; 0567 } 0568 } 0569 svgFile.close(); 0570 } 0571 0572 if (!styleSvgStr.isEmpty()) { 0573 int styleInd = styleSvgStr.indexOf("style="); 0574 QString cleanedStr = styleSvgStr.remove(0, styleInd+7); 0575 int endInd = cleanedStr.indexOf("\""); 0576 styleSvgStr = cleanedStr.mid(0,endInd); 0577 0578 QStringList styleValues = styleSvgStr.split(";"); 0579 // qDebug() << "plasma theme, discovered svg style ::: " << styleValues; 0580 0581 float opacity{1}; 0582 float fillOpacity{1}; 0583 0584 for (QString &value : styleValues) { 0585 if (value.startsWith("opacity:")) { 0586 opacity = value.remove(0,8).toFloat(); 0587 } 0588 if (value.startsWith("fill-opacity:")) { 0589 fillOpacity = value.remove(0,13).toFloat(); 0590 } 0591 } 0592 0593 // m_backgroundMaxOpacity = opacity * fillOpacity; 0594 0595 // qDebug() << "plasma theme opacity :: " << m_backgroundMaxOpacity << " from : " << opacity << " * " << fillOpacity; 0596 } 0597 0598 // emit backgroundMaxOpacityChanged(); 0599 } 0600 0601 void Theme::loadThemeLightness() 0602 { 0603 float textColorLum = Latte::colorLumina(m_defaultScheme->textColor()); 0604 float backColorLum = Latte::colorLumina(m_defaultScheme->backgroundColor()); 0605 0606 if (backColorLum > textColorLum) { 0607 m_isLightTheme = true; 0608 } else { 0609 m_isLightTheme = false; 0610 } 0611 0612 if (m_isLightTheme) { 0613 qDebug() << "Plasma theme is light..."; 0614 } else { 0615 qDebug() << "Plasma theme is dark..."; 0616 } 0617 } 0618 0619 void Theme::loadConfig() 0620 { 0621 setOutlineWidth(m_themeGroup.readEntry("outlineWidth", 1)); 0622 } 0623 0624 void Theme::saveConfig() 0625 { 0626 m_themeGroup.writeEntry("outlineWidth", m_outlineWidth); 0627 0628 m_themeGroup.sync(); 0629 } 0630 0631 } 0632 }