File indexing completed on 2024-04-21 03:42:36
0001 /* 0002 This file is a part of digiKam project 0003 http://www.digikam.org 0004 0005 SPDX-FileCopyrightText: 2006-2018 Gilles Caulier <caulier dot gilles at gmail dot com> 0006 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad at users dot sourceforge dot net> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "schememanager.h" 0012 0013 #include <kconfig.h> 0014 #include <kconfiggroup.h> 0015 0016 #include <QWidget> 0017 #include <QPainter> 0018 0019 #include <cmath> 0020 0021 class HCYColorSpace 0022 { 0023 public: 0024 0025 explicit HCYColorSpace(const QColor&); 0026 explicit HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0); 0027 0028 QColor qColor() const; 0029 0030 static qreal luma(const QColor &); 0031 0032 public: 0033 0034 qreal h; 0035 qreal c; 0036 qreal y; 0037 qreal a; 0038 0039 private: 0040 0041 static qreal gamma(qreal); 0042 static qreal igamma(qreal); 0043 static qreal lumag(qreal, qreal, qreal); 0044 }; 0045 0046 // ------------------------------------------------------------------------------ 0047 0048 namespace ColorTools 0049 { 0050 0051 static inline qreal wrap(qreal a, qreal d = 1.0) 0052 { 0053 qreal r = fmod(a, d); 0054 0055 return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0)); 0056 } 0057 0058 /* 0059 normalize: like qBound(a, 0.0, 1.0) but without needing the args and with 0060 "safer" behavior on NaN (isnan(a) -> return 0.0) 0061 */ 0062 static inline qreal normalize(qreal a) 0063 { 0064 return (a < 1.0 ? (a > 0.0 ? a : 0.0) : 1.0); 0065 } 0066 0067 static inline qreal mixQreal(qreal a, qreal b, qreal bias) 0068 { 0069 return (a + (b - a) * bias); 0070 } 0071 0072 /** 0073 * Calculate the luma of a color. Luma is weighted sum of gamma-adjusted 0074 * R'G'B' components of a color. The result is similar to qGray. The range 0075 * is from 0.0 (black) to 1.0 (white). 0076 * 0077 */ 0078 qreal luma(const QColor& color) 0079 { 0080 return HCYColorSpace::luma(color); 0081 } 0082 0083 /** 0084 * Calculate hue, chroma and luma of a color in one call. 0085 */ 0086 void getHcy(const QColor& color, qreal* h, qreal* c, qreal* y, qreal* a = 0) 0087 { 0088 if (!c || !h || !y) 0089 { 0090 return; 0091 } 0092 0093 HCYColorSpace khcy(color); 0094 *c = khcy.c; 0095 *h = khcy.h; 0096 *y = khcy.y; 0097 0098 if (a) 0099 { 0100 *a = khcy.a; 0101 } 0102 } 0103 0104 /** 0105 * Calculate the contrast ratio between two colors, according to the 0106 * W3C/WCAG2.0 algorithm, (Lmax + 0.05)/(Lmin + 0.05), where Lmax and Lmin 0107 * are the luma values of the lighter color and the darker color, 0108 * respectively. 0109 * 0110 * A contrast ration of 5:1 (result == 5.0) is the minimum for "normal" 0111 * text to be considered readable (large text can go as low as 3:1). The 0112 * ratio ranges from 1:1 (result == 1.0) to 21:1 (result == 21.0). 0113 */ 0114 static qreal contrastRatioForLuma(qreal y1, qreal y2) 0115 { 0116 if (y1 > y2) 0117 { 0118 return (y1 + 0.05) / (y2 + 0.05); 0119 } 0120 0121 return (y2 + 0.05) / (y1 + 0.05); 0122 } 0123 0124 qreal contrastRatio(const QColor& c1, const QColor& c2) 0125 { 0126 return contrastRatioForLuma(luma(c1), luma(c2)); 0127 } 0128 0129 /** 0130 * Adjust the luma of a color by changing its distance from white. 0131 */ 0132 QColor lighten(const QColor& color, qreal ky = 0.5, qreal kc = 1.0) 0133 { 0134 HCYColorSpace c(color); 0135 c.y = 1.0 - ColorTools::normalize((1.0 - c.y) * (1.0 - ky)); 0136 c.c = 1.0 - ColorTools::normalize((1.0 - c.c) * kc); 0137 0138 return c.qColor(); 0139 } 0140 0141 /** 0142 * Adjust the luma of a color by changing its distance from black. 0143 */ 0144 QColor darken(const QColor& color, qreal ky = 0.5, qreal kc = 1.0) 0145 { 0146 HCYColorSpace c(color); 0147 c.y = ColorTools::normalize(c.y * (1.0 - ky)); 0148 c.c = ColorTools::normalize(c.c * kc); 0149 0150 return c.qColor(); 0151 } 0152 0153 /** 0154 * Adjust the luma and chroma components of a color. The amount is added 0155 * to the corresponding component. 0156 */ 0157 QColor shade(const QColor& color, qreal ky, qreal kc = 0.0) 0158 { 0159 HCYColorSpace c(color); 0160 c.y = ColorTools::normalize(c.y + ky); 0161 c.c = ColorTools::normalize(c.c + kc); 0162 0163 return c.qColor(); 0164 } 0165 0166 /** 0167 * Blend two colors into a new color by linear combination. 0168 */ 0169 QColor mix(const QColor& c1, const QColor& c2, qreal bias) 0170 { 0171 if (bias <= 0.0) 0172 { 0173 return c1; 0174 } 0175 0176 if (bias >= 1.0) 0177 { 0178 return c2; 0179 } 0180 0181 if (qIsNaN(bias)) 0182 { 0183 return c1; 0184 } 0185 0186 qreal r = mixQreal(c1.redF(), c2.redF(), bias); 0187 qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); 0188 qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); 0189 qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); 0190 0191 return QColor::fromRgbF(r, g, b, a); 0192 } 0193 0194 static QColor tintHelper(const QColor& base, qreal baseLuma, const QColor& color, qreal amount) 0195 { 0196 HCYColorSpace result(mix(base, color, pow(amount, 0.3))); 0197 result.y = mixQreal(baseLuma, result.y, amount); 0198 0199 return result.qColor(); 0200 } 0201 0202 /** 0203 * Create a new color by tinting one color with another. This function is 0204 * meant for creating additional colors withings the same class (background, 0205 * foreground) from colors in a different class. Therefore when @p amount 0206 * is low, the luma of @p base is mostly preserved, while the hue and 0207 * chroma of @p color is mostly inherited. 0208 * 0209 * @param base color to be tinted 0210 * @param color color with which to tint 0211 * @param amount how strongly to tint the base; 0.0 gives @p base, 0212 * 1.0 gives @p color 0213 */ 0214 QColor tint(const QColor& base, const QColor& color, qreal amount = 0.3) 0215 { 0216 if (amount <= 0.0) 0217 { 0218 return base; 0219 } 0220 0221 if (amount >= 1.0) 0222 { 0223 return color; 0224 } 0225 0226 if (qIsNaN(amount)) 0227 { 0228 return base; 0229 } 0230 0231 qreal baseLuma = luma(base); //cache value because luma call is expensive 0232 double ri = contrastRatioForLuma(baseLuma, luma(color)); 0233 double rg = 1.0 + ((ri + 1.0) * amount * amount * amount); 0234 double u = 1.0, l = 0.0; 0235 QColor result; 0236 0237 for (int i = 12; i; --i) 0238 { 0239 double a = 0.5 * (l + u); 0240 result = tintHelper(base, baseLuma, color, a); 0241 double ra = contrastRatioForLuma(baseLuma, luma(result)); 0242 0243 if (ra > rg) 0244 { 0245 u = a; 0246 } 0247 else 0248 { 0249 l = a; 0250 } 0251 } 0252 0253 return result; 0254 } 0255 0256 /** 0257 * Blend two colors into a new color by painting the second color over the 0258 * first using the specified composition mode. 0259 * 0260 * @param base the base color (alpha channel is ignored). 0261 * @param paint the color to be overlayed onto the base color. 0262 * @param comp the CompositionMode used to do the blending. 0263 */ 0264 QColor overlayColors(const QColor& base, const QColor& paint, 0265 QPainter::CompositionMode comp) 0266 { 0267 // This isn't the fastest way, but should be "fast enough". 0268 // It's also the only safe way to use QPainter::CompositionMode 0269 QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); 0270 QPainter p(&img); 0271 QColor start = base; 0272 start.setAlpha(255); // opaque 0273 p.fillRect(0, 0, 1, 1, start); 0274 p.setCompositionMode(comp); 0275 p.fillRect(0, 0, 1, 1, paint); 0276 p.end(); 0277 0278 return img.pixel(0, 0); 0279 } 0280 0281 } // namespace ColorTools 0282 0283 // ------------------------------------------------------------------------------ 0284 0285 #define HCY_REC 709 // use 709 for now 0286 #if HCY_REC == 601 0287 static const qreal yc[3] = {0.299, 0.587, 0.114 }; 0288 #elif HCY_REC == 709 0289 static const qreal yc[3] = {0.2126, 0.7152, 0.0722 }; 0290 #else // use Qt values 0291 static const qreal yc[3] = {0.34375, 0.5, 0.15625}; 0292 #endif 0293 0294 qreal HCYColorSpace::gamma(qreal n) 0295 { 0296 return pow(ColorTools::normalize(n), 2.2); 0297 } 0298 0299 qreal HCYColorSpace::igamma(qreal n) 0300 { 0301 return pow(ColorTools::normalize(n), 1.0 / 2.2); 0302 } 0303 0304 qreal HCYColorSpace::lumag(qreal r, qreal g, qreal b) 0305 { 0306 return r * yc[0] + g * yc[1] + b * yc[2]; 0307 } 0308 0309 HCYColorSpace::HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_) 0310 { 0311 h = h_; 0312 c = c_; 0313 y = y_; 0314 a = a_; 0315 } 0316 0317 HCYColorSpace::HCYColorSpace(const QColor& color) 0318 { 0319 qreal r = gamma(color.redF()); 0320 qreal g = gamma(color.greenF()); 0321 qreal b = gamma(color.blueF()); 0322 a = color.alphaF(); 0323 0324 // luma component 0325 y = lumag(r, g, b); 0326 0327 // hue component 0328 qreal p = qMax(qMax(r, g), b); 0329 qreal n = qMin(qMin(r, g), b); 0330 qreal d = 6.0 * (p - n); 0331 0332 if (n == p) 0333 { 0334 h = 0.0; 0335 } 0336 else if (r == p) 0337 { 0338 h = ((g - b) / d); 0339 } 0340 else if (g == p) 0341 { 0342 h = ((b - r) / d) + (1.0 / 3.0); 0343 } 0344 else 0345 { 0346 h = ((r - g) / d) + (2.0 / 3.0); 0347 } 0348 0349 // chroma component 0350 if (r == g && g == b) 0351 { 0352 c = 0.0; 0353 } 0354 else 0355 { 0356 c = qMax((y - n) / y, (p - y) / (1 - y)); 0357 } 0358 } 0359 0360 QColor HCYColorSpace::qColor() const 0361 { 0362 // start with sane component values 0363 qreal _h = ColorTools::wrap(h); 0364 qreal _c = ColorTools::normalize(c); 0365 qreal _y = ColorTools::normalize(y); 0366 0367 // calculate some needed variables 0368 qreal _hs = _h * 6.0, th, tm; 0369 0370 if (_hs < 1.0) 0371 { 0372 th = _hs; 0373 tm = yc[0] + yc[1] * th; 0374 } 0375 else if (_hs < 2.0) 0376 { 0377 th = 2.0 - _hs; 0378 tm = yc[1] + yc[0] * th; 0379 } 0380 else if (_hs < 3.0) 0381 { 0382 th = _hs - 2.0; 0383 tm = yc[1] + yc[2] * th; 0384 } 0385 else if (_hs < 4.0) 0386 { 0387 th = 4.0 - _hs; 0388 tm = yc[2] + yc[1] * th; 0389 } 0390 else if (_hs < 5.0) 0391 { 0392 th = _hs - 4.0; 0393 tm = yc[2] + yc[0] * th; 0394 } 0395 else 0396 { 0397 th = 6.0 - _hs; 0398 tm = yc[0] + yc[2] * th; 0399 } 0400 0401 // calculate RGB channels in sorted order 0402 qreal tn, to, tp; 0403 0404 if (tm >= _y) 0405 { 0406 tp = _y + _y * _c * (1.0 - tm) / tm; 0407 to = _y + _y * _c * (th - tm) / tm; 0408 tn = _y - (_y * _c); 0409 } 0410 else 0411 { 0412 tp = _y + (1.0 - _y) * _c; 0413 to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm); 0414 tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm); 0415 } 0416 0417 // return RGB channels in appropriate order 0418 if (_hs < 1.0) 0419 { 0420 return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a); 0421 } 0422 else if (_hs < 2.0) 0423 { 0424 return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a); 0425 } 0426 else if (_hs < 3.0) 0427 { 0428 return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a); 0429 } 0430 else if (_hs < 4.0) 0431 { 0432 return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a); 0433 } 0434 else if (_hs < 5.0) 0435 { 0436 return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a); 0437 } 0438 else 0439 { 0440 return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a); 0441 } 0442 } 0443 0444 qreal HCYColorSpace::luma(const QColor& color) 0445 { 0446 return lumag(gamma(color.redF()), 0447 gamma(color.greenF()), 0448 gamma(color.blueF())); 0449 } 0450 0451 // ------------------------------------------------------------------------------------- 0452 0453 class StateEffects 0454 { 0455 public: 0456 0457 explicit StateEffects(QPalette::ColorGroup state, const KSharedConfigPtr&); 0458 ~StateEffects() = default; 0459 0460 QBrush brush(const QBrush& background) const; 0461 QBrush brush(const QBrush& foreground, const QBrush& background) const; 0462 0463 private: 0464 0465 enum Effects 0466 { 0467 // Effects 0468 Intensity = 0, 0469 Color = 1, 0470 Contrast = 2, 0471 // Intensity 0472 IntensityNoEffect = 0, 0473 IntensityShade = 1, 0474 IntensityDarken = 2, 0475 IntensityLighten = 3, 0476 // Color 0477 ColorNoEffect = 0, 0478 ColorDesaturate = 1, 0479 ColorFade = 2, 0480 ColorTint = 3, 0481 // Contrast 0482 ContrastNoEffect = 0, 0483 ContrastFade = 1, 0484 ContrastTint = 2 0485 }; 0486 0487 private: 0488 0489 int _effects[3]; 0490 double _amount[3]; 0491 QColor _color; 0492 }; 0493 0494 StateEffects::StateEffects(QPalette::ColorGroup state, const KSharedConfigPtr& config) 0495 : _color(0, 0, 0, 0) 0496 { 0497 QString group; 0498 0499 if (state == QPalette::Disabled) 0500 { 0501 group = QLatin1String("ColorEffects:Disabled"); 0502 } 0503 else if (state == QPalette::Inactive) 0504 { 0505 group = QLatin1String("ColorEffects:Inactive"); 0506 } 0507 0508 _effects[0] = 0; 0509 _effects[1] = 0; 0510 _effects[2] = 0; 0511 0512 if (!group.isEmpty()) 0513 { 0514 KConfigGroup cfg(config, group); 0515 const bool enabledByDefault = (state == QPalette::Disabled); 0516 0517 if (cfg.readEntry("Enable", enabledByDefault)) 0518 { 0519 _effects[Intensity] = cfg.readEntry("IntensityEffect", (int)((state == QPalette::Disabled) ? IntensityDarken : IntensityNoEffect)); 0520 _effects[Color] = cfg.readEntry("ColorEffect", (int)((state == QPalette::Disabled) ? ColorNoEffect : ColorDesaturate)); 0521 _effects[Contrast] = cfg.readEntry("ContrastEffect", (int)((state == QPalette::Disabled) ? ContrastFade : ContrastTint)); 0522 _amount[Intensity] = cfg.readEntry("IntensityAmount", (state == QPalette::Disabled) ? 0.10 : 0.0); 0523 _amount[Color] = cfg.readEntry("ColorAmount", (state == QPalette::Disabled) ? 0.0 : -0.9); 0524 _amount[Contrast] = cfg.readEntry("ContrastAmount", (state == QPalette::Disabled) ? 0.65 : 0.25); 0525 0526 if (_effects[Color] > ColorNoEffect) 0527 { 0528 _color = cfg.readEntry("Color", (state == QPalette::Disabled) ? QColor(56, 56, 56) 0529 : QColor(112, 111, 110)); 0530 } 0531 } 0532 } 0533 } 0534 0535 QBrush StateEffects::brush(const QBrush& background) const 0536 { 0537 QColor color = background.color(); // TODO - actually work on brushes 0538 0539 switch (_effects[Intensity]) 0540 { 0541 case IntensityShade: 0542 color = ColorTools::shade(color, _amount[Intensity]); 0543 break; 0544 case IntensityDarken: 0545 color = ColorTools::darken(color, _amount[Intensity]); 0546 break; 0547 case IntensityLighten: 0548 color = ColorTools::lighten(color, _amount[Intensity]); 0549 break; 0550 } 0551 0552 switch (_effects[Color]) 0553 { 0554 case ColorDesaturate: 0555 color = ColorTools::darken(color, 0.0, 1.0 - _amount[Color]); 0556 break; 0557 case ColorFade: 0558 color = ColorTools::mix(color, _color, _amount[Color]); 0559 break; 0560 case ColorTint: 0561 color = ColorTools::tint(color, _color, _amount[Color]); 0562 break; 0563 } 0564 0565 return QBrush(color); 0566 } 0567 0568 QBrush StateEffects::brush(const QBrush& foreground, const QBrush& background) const 0569 { 0570 QColor color = foreground.color(); 0571 QColor bg = background.color(); 0572 0573 // Apply the foreground effects 0574 0575 switch (_effects[Contrast]) 0576 { 0577 case ContrastFade: 0578 color = ColorTools::mix(color, bg, _amount[Contrast]); 0579 break; 0580 case ContrastTint: 0581 color = ColorTools::tint(color, bg, _amount[Contrast]); 0582 break; 0583 } 0584 0585 // Now apply global effects 0586 0587 return brush(color); 0588 } 0589 0590 // ------------------------------------------------------------------------------------ 0591 0592 struct SetDefaultColors 0593 { 0594 int NormalBackground[3]; 0595 int AlternateBackground[3]; 0596 int NormalText[3]; 0597 int InactiveText[3]; 0598 int ActiveText[3]; 0599 int LinkText[3]; 0600 int VisitedText[3]; 0601 int NegativeText[3]; 0602 int NeutralText[3]; 0603 int PositiveText[3]; 0604 }; 0605 0606 struct DecoDefaultColors 0607 { 0608 int Hover[3]; 0609 int Focus[3]; 0610 }; 0611 0612 // these numbers come from the Breeze color scheme 0613 static const SetDefaultColors defaultViewColors = 0614 { 0615 { 252, 252, 252 }, // Background 0616 { 239, 240, 241 }, // Alternate 0617 { 49, 54, 59 }, // Normal 0618 { 127, 140, 141 }, // Inactive 0619 { 61, 174, 233 }, // Active 0620 { 41, 128, 185 }, // Link 0621 { 127, 140, 141 }, // Visited 0622 { 218, 68, 83 }, // Negative 0623 { 246, 116, 0 }, // Neutral 0624 { 39, 174, 96 } // Positive 0625 }; 0626 0627 static const SetDefaultColors defaultWindowColors = 0628 { 0629 { 239, 240, 241 }, // Background 0630 { 189, 195, 199 }, // Alternate 0631 { 49, 54, 59 }, // Normal 0632 { 127, 140, 141 }, // Inactive 0633 { 61, 174, 233 }, // Active 0634 { 41, 128, 185 }, // Link 0635 { 127, 140, 141 }, // Visited 0636 { 218, 68, 83 }, // Negative 0637 { 246, 116, 0 }, // Neutral 0638 { 39, 174, 96 } // Positive 0639 }; 0640 0641 static const SetDefaultColors defaultButtonColors = 0642 { 0643 { 239, 240, 241 }, // Background 0644 { 189, 195, 199 }, // Alternate 0645 { 49, 54, 59 }, // Normal 0646 { 127, 140, 141 }, // Inactive 0647 { 61, 174, 233 }, // Active 0648 { 41, 128, 185 }, // Link 0649 { 127, 140, 141 }, // Visited 0650 { 218, 68, 83 }, // Negative 0651 { 246, 116, 0 }, // Neutral 0652 { 39, 174, 96 } // Positive 0653 }; 0654 0655 static const SetDefaultColors defaultSelectionColors = 0656 { 0657 { 61, 174, 233 }, // Background 0658 { 29, 153, 243 }, // Alternate 0659 { 239, 240, 241 }, // Normal 0660 { 239, 240, 241 }, // Inactive 0661 { 252, 252, 252 }, // Active 0662 { 253, 188, 75 }, // Link 0663 { 189, 195, 199 }, // Visited 0664 { 218, 68, 83 }, // Negative 0665 { 246, 116, 0 }, // Neutral 0666 { 39, 174, 96 } // Positive 0667 }; 0668 0669 static const SetDefaultColors defaultTooltipColors = 0670 { 0671 { 49, 54, 59 }, // Background 0672 { 77, 77, 77 }, // Alternate 0673 { 239, 240, 241 }, // Normal 0674 { 189, 195, 199 }, // Inactive 0675 { 61, 174, 233 }, // Active 0676 { 41, 128, 185 }, // Link 0677 { 127, 140, 141 }, // Visited 0678 { 218, 68, 83 }, // Negative 0679 { 246, 116, 0 }, // Neutral 0680 { 39, 174, 96 } // Positive 0681 }; 0682 0683 static const SetDefaultColors defaultComplementaryColors = 0684 { 0685 { 49, 54, 59 }, // Background 0686 { 77, 77, 77 }, // Alternate 0687 { 239, 240, 241 }, // Normal 0688 { 189, 195, 199 }, // Inactive 0689 { 61, 174, 233 }, // Active 0690 { 41, 128, 185 }, // Link 0691 { 127, 140, 141 }, // Visited 0692 { 218, 68, 83 }, // Negative 0693 { 246, 116, 0 }, // Neutral 0694 { 39, 174, 96 } // Positive 0695 }; 0696 0697 static const DecoDefaultColors defaultDecorationColors = 0698 { 0699 { 147, 206, 233 }, // Hover 0700 { 61, 174, 233 }, // Focus 0701 }; 0702 0703 // ------------------------------------------------------------------------------------ 0704 0705 class SchemeManagerPrivate : public QSharedData 0706 { 0707 public: 0708 0709 explicit SchemeManagerPrivate(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors); 0710 explicit SchemeManagerPrivate(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors, const QBrush&); 0711 ~SchemeManagerPrivate() = default; 0712 0713 QBrush background(SchemeManager::BackgroundRole) const; 0714 QBrush foreground(SchemeManager::ForegroundRole) const; 0715 QBrush decoration(SchemeManager::DecorationRole) const; 0716 qreal contrast() const; 0717 0718 private: 0719 0720 void init(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors); 0721 0722 private: 0723 0724 struct 0725 { 0726 QBrush fg[8], 0727 bg[8], 0728 deco[2]; 0729 } _brushes; 0730 0731 qreal _contrast; 0732 }; 0733 0734 #define DEFAULT(c) QColor( c[0], c[1], c[2] ) 0735 #define SET_DEFAULT(a) DEFAULT( defaults.a ) 0736 #define DECO_DEFAULT(a) DEFAULT( defaultDecorationColors.a ) 0737 0738 SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config, 0739 QPalette::ColorGroup state, 0740 const char* group, 0741 SetDefaultColors defaults) 0742 { 0743 KConfigGroup cfg(config, group); 0744 _contrast = SchemeManager::contrastF(config); 0745 0746 // loaded-from-config colors (no adjustment) 0747 _brushes.bg[0] = cfg.readEntry("BackgroundNormal", SET_DEFAULT(NormalBackground)); 0748 _brushes.bg[1] = cfg.readEntry("BackgroundAlternate", SET_DEFAULT(AlternateBackground)); 0749 0750 // the rest 0751 init(config, state, group, defaults); 0752 } 0753 0754 SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config, 0755 QPalette::ColorGroup state, 0756 const char* group, 0757 SetDefaultColors defaults, 0758 const QBrush& tint) 0759 { 0760 KConfigGroup cfg(config, group); 0761 _contrast = SchemeManager::contrastF(config); 0762 0763 // loaded-from-config colors 0764 _brushes.bg[0] = cfg.readEntry("BackgroundNormal", SET_DEFAULT(NormalBackground)); 0765 _brushes.bg[1] = cfg.readEntry("BackgroundAlternate", SET_DEFAULT(AlternateBackground)); 0766 0767 // adjustment 0768 _brushes.bg[0] = ColorTools::tint(_brushes.bg[0].color(), tint.color(), 0.4); 0769 _brushes.bg[1] = ColorTools::tint(_brushes.bg[1].color(), tint.color(), 0.4); 0770 0771 // the rest 0772 init(config, state, group, defaults); 0773 } 0774 0775 void SchemeManagerPrivate::init(const KSharedConfigPtr& config, 0776 QPalette::ColorGroup state, 0777 const char* group, 0778 SetDefaultColors defaults) 0779 { 0780 KConfigGroup cfg(config, group); 0781 0782 // loaded-from-config colors 0783 _brushes.fg[0] = cfg.readEntry("ForegroundNormal", SET_DEFAULT(NormalText)); 0784 _brushes.fg[1] = cfg.readEntry("ForegroundInactive", SET_DEFAULT(InactiveText)); 0785 _brushes.fg[2] = cfg.readEntry("ForegroundActive", SET_DEFAULT(ActiveText)); 0786 _brushes.fg[3] = cfg.readEntry("ForegroundLink", SET_DEFAULT(LinkText)); 0787 _brushes.fg[4] = cfg.readEntry("ForegroundVisited", SET_DEFAULT(VisitedText)); 0788 _brushes.fg[5] = cfg.readEntry("ForegroundNegative", SET_DEFAULT(NegativeText)); 0789 _brushes.fg[6] = cfg.readEntry("ForegroundNeutral", SET_DEFAULT(NeutralText)); 0790 _brushes.fg[7] = cfg.readEntry("ForegroundPositive", SET_DEFAULT(PositiveText)); 0791 _brushes.deco[0] = cfg.readEntry("DecorationHover", DECO_DEFAULT(Hover)); 0792 _brushes.deco[1] = cfg.readEntry("DecorationFocus", DECO_DEFAULT(Focus)); 0793 0794 // apply state adjustments 0795 0796 if (state != QPalette::Active) 0797 { 0798 StateEffects effects(state, config); 0799 0800 for (auto &brush : _brushes.fg) 0801 { 0802 brush = effects.brush(brush, _brushes.bg[0]); 0803 } 0804 0805 _brushes.deco[0] = effects.brush(_brushes.deco[0], _brushes.bg[0]); 0806 _brushes.deco[1] = effects.brush(_brushes.deco[1], _brushes.bg[0]); 0807 _brushes.bg[0] = effects.brush(_brushes.bg[0]); 0808 _brushes.bg[1] = effects.brush(_brushes.bg[1]); 0809 } 0810 0811 // calculated backgrounds 0812 _brushes.bg[2] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[2].color()); 0813 _brushes.bg[3] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[3].color()); 0814 _brushes.bg[4] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[4].color()); 0815 _brushes.bg[5] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[5].color()); 0816 _brushes.bg[6] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[6].color()); 0817 _brushes.bg[7] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[7].color()); 0818 } 0819 0820 QBrush SchemeManagerPrivate::background(SchemeManager::BackgroundRole role) const 0821 { 0822 switch (role) 0823 { 0824 case SchemeManager::AlternateBackground: 0825 return _brushes.bg[1]; 0826 case SchemeManager::ActiveBackground: 0827 return _brushes.bg[2]; 0828 case SchemeManager::LinkBackground: 0829 return _brushes.bg[3]; 0830 case SchemeManager::VisitedBackground: 0831 return _brushes.bg[4]; 0832 case SchemeManager::NegativeBackground: 0833 return _brushes.bg[5]; 0834 case SchemeManager::NeutralBackground: 0835 return _brushes.bg[6]; 0836 case SchemeManager::PositiveBackground: 0837 return _brushes.bg[7]; 0838 default: 0839 return _brushes.bg[0]; 0840 } 0841 } 0842 0843 QBrush SchemeManagerPrivate::foreground(SchemeManager::ForegroundRole role) const 0844 { 0845 switch (role) 0846 { 0847 case SchemeManager::InactiveText: 0848 return _brushes.fg[1]; 0849 case SchemeManager::ActiveText: 0850 return _brushes.fg[2]; 0851 case SchemeManager::LinkText: 0852 return _brushes.fg[3]; 0853 case SchemeManager::VisitedText: 0854 return _brushes.fg[4]; 0855 case SchemeManager::NegativeText: 0856 return _brushes.fg[5]; 0857 case SchemeManager::NeutralText: 0858 return _brushes.fg[6]; 0859 case SchemeManager::PositiveText: 0860 return _brushes.fg[7]; 0861 default: 0862 return _brushes.fg[0]; 0863 } 0864 } 0865 0866 QBrush SchemeManagerPrivate::decoration(SchemeManager::DecorationRole role) const 0867 { 0868 switch (role) 0869 { 0870 case SchemeManager::FocusColor: 0871 return _brushes.deco[1]; 0872 default: 0873 return _brushes.deco[0]; 0874 } 0875 } 0876 0877 qreal SchemeManagerPrivate::contrast() const 0878 { 0879 return _contrast; 0880 } 0881 0882 // ------------------------------------------------------------------------------------ 0883 0884 SchemeManager::SchemeManager(const SchemeManager& other) : d(other.d) 0885 { 0886 } 0887 0888 SchemeManager::~SchemeManager() 0889 { 0890 } 0891 0892 SchemeManager& SchemeManager::operator=(const SchemeManager& other) 0893 { 0894 d = other.d; 0895 return *this; 0896 } 0897 0898 SchemeManager::SchemeManager(QPalette::ColorGroup state, ColorSet set, KSharedConfigPtr config) 0899 { 0900 if (!config) 0901 { 0902 config = KSharedConfig::openConfig(); 0903 } 0904 0905 switch (set) 0906 { 0907 case Window: 0908 d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors); 0909 break; 0910 case Button: 0911 d = new SchemeManagerPrivate(config, state, "Colors:Button", defaultButtonColors); 0912 break; 0913 case Selection: 0914 { 0915 KConfigGroup group(config, "ColorEffects:Inactive"); 0916 // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp 0917 bool inactiveSelectionEffect = group.readEntry("ChangeSelectionColor", group.readEntry("Enable", true)); 0918 // if enabled, inactiver/disabled uses Window colors instead, ala gtk 0919 // ...except tinted with the Selection:NormalBackground color so it looks more like selection 0920 if (state == QPalette::Active || (state == QPalette::Inactive && !inactiveSelectionEffect)) 0921 { 0922 d = new SchemeManagerPrivate(config, state, "Colors:Selection", defaultSelectionColors); 0923 } 0924 else if (state == QPalette::Inactive) 0925 { 0926 d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors, 0927 SchemeManager(QPalette::Active, Selection, config).background()); 0928 } 0929 else 0930 { 0931 // disabled (...and still want this branch when inactive+disabled exists) 0932 d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors); 0933 } 0934 } 0935 break; 0936 case Tooltip: 0937 d = new SchemeManagerPrivate(config, state, "Colors:Tooltip", defaultTooltipColors); 0938 break; 0939 case Complementary: 0940 d = new SchemeManagerPrivate(config, state, "Colors:Complementary", defaultComplementaryColors); 0941 break; 0942 default: 0943 d = new SchemeManagerPrivate(config, state, "Colors:View", defaultViewColors); 0944 } 0945 } 0946 0947 int SchemeManager::contrast() 0948 { 0949 KConfigGroup g(KSharedConfig::openConfig(), "KDE"); 0950 0951 return g.readEntry("contrast", 7); 0952 } 0953 0954 qreal SchemeManager::contrastF(const KSharedConfigPtr& config) 0955 { 0956 if (config) 0957 { 0958 KConfigGroup g(config, "KDE"); 0959 0960 return 0.1 * g.readEntry("contrast", 7); 0961 } 0962 0963 return 0.1 * (qreal)contrast(); 0964 } 0965 0966 QBrush SchemeManager::background(BackgroundRole role) const 0967 { 0968 return d->background(role); 0969 } 0970 0971 QBrush SchemeManager::foreground(ForegroundRole role) const 0972 { 0973 return d->foreground(role); 0974 } 0975 0976 QBrush SchemeManager::decoration(DecorationRole role) const 0977 { 0978 return d->decoration(role); 0979 } 0980 0981 QColor SchemeManager::shade(ShadeRole role) const 0982 { 0983 return shade(background().color(), role, d->contrast()); 0984 } 0985 0986 QColor SchemeManager::shade(const QColor& color, ShadeRole role) 0987 { 0988 return shade(color, role, SchemeManager::contrastF()); 0989 } 0990 0991 QColor SchemeManager::shade(const QColor& color, ShadeRole role, qreal contrast, qreal chromaAdjust) 0992 { 0993 // nan -> 1.0 0994 contrast = ((1.0 > contrast) ? ((-1.0 < contrast) ? contrast 0995 : -1.0) 0996 : 1.0); 0997 qreal y = ColorTools::luma(color); 0998 qreal yi = 1.0 - y; 0999 1000 // handle very dark colors (base, mid, dark, shadow == midlight, light) 1001 if (y < 0.006) 1002 { 1003 switch (role) 1004 { 1005 case SchemeManager::LightShade: 1006 return ColorTools::shade(color, 0.05 + 0.95 * contrast, chromaAdjust); 1007 case SchemeManager::MidShade: 1008 return ColorTools::shade(color, 0.01 + 0.20 * contrast, chromaAdjust); 1009 case SchemeManager::DarkShade: 1010 return ColorTools::shade(color, 0.02 + 0.40 * contrast, chromaAdjust); 1011 default: 1012 return ColorTools::shade(color, 0.03 + 0.60 * contrast, chromaAdjust); 1013 } 1014 } 1015 1016 // handle very light colors (base, midlight, light == mid, dark, shadow) 1017 if (y > 0.93) 1018 { 1019 switch (role) 1020 { 1021 case SchemeManager::MidlightShade: 1022 return ColorTools::shade(color, -0.02 - 0.20 * contrast, chromaAdjust); 1023 case SchemeManager::DarkShade: 1024 return ColorTools::shade(color, -0.06 - 0.60 * contrast, chromaAdjust); 1025 case SchemeManager::ShadowShade: 1026 return ColorTools::shade(color, -0.10 - 0.90 * contrast, chromaAdjust); 1027 default: 1028 return ColorTools::shade(color, -0.04 - 0.40 * contrast, chromaAdjust); 1029 } 1030 } 1031 1032 // handle everything else 1033 qreal lightAmount = (0.05 + y * 0.55) * (0.25 + contrast * 0.75); 1034 qreal darkAmount = (- y) * (0.55 + contrast * 0.35); 1035 1036 switch (role) 1037 { 1038 case SchemeManager::LightShade: 1039 return ColorTools::shade(color, lightAmount, chromaAdjust); 1040 case SchemeManager::MidlightShade: 1041 return ColorTools::shade(color, (0.15 + 0.35 * yi) * lightAmount, chromaAdjust); 1042 case SchemeManager::MidShade: 1043 return ColorTools::shade(color, (0.35 + 0.15 * y) * darkAmount, chromaAdjust); 1044 case SchemeManager::DarkShade: 1045 return ColorTools::shade(color, darkAmount, chromaAdjust); 1046 default: 1047 return ColorTools::darken(ColorTools::shade(color, darkAmount, chromaAdjust), 0.5 + 0.3 * y); 1048 } 1049 } 1050 1051 void SchemeManager::adjustBackground(QPalette& palette, BackgroundRole newRole, QPalette::ColorRole color, 1052 ColorSet set, KSharedConfigPtr config) 1053 { 1054 palette.setBrush(QPalette::Active, color, SchemeManager(QPalette::Active, set, config).background(newRole)); 1055 palette.setBrush(QPalette::Inactive, color, SchemeManager(QPalette::Inactive, set, config).background(newRole)); 1056 palette.setBrush(QPalette::Disabled, color, SchemeManager(QPalette::Disabled, set, config).background(newRole)); 1057 } 1058 1059 void SchemeManager::adjustForeground(QPalette& palette, ForegroundRole newRole, QPalette::ColorRole color, 1060 ColorSet set, KSharedConfigPtr config) 1061 { 1062 palette.setBrush(QPalette::Active, color, SchemeManager(QPalette::Active, set, config).foreground(newRole)); 1063 palette.setBrush(QPalette::Inactive, color, SchemeManager(QPalette::Inactive, set, config).foreground(newRole)); 1064 palette.setBrush(QPalette::Disabled, color, SchemeManager(QPalette::Disabled, set, config).foreground(newRole)); 1065 } 1066 1067 QPalette SchemeManager::createApplicationPalette(const KSharedConfigPtr& config) 1068 { 1069 QPalette palette; 1070 1071 static const QPalette::ColorGroup states[3] = 1072 { 1073 QPalette::Active, 1074 QPalette::Inactive, 1075 QPalette::Disabled 1076 }; 1077 1078 // TT thinks tooltips shouldn't use active, so we use our active colors for all states 1079 SchemeManager schemeTooltip(QPalette::Active, SchemeManager::Tooltip, config); 1080 1081 for (auto &state : states) 1082 { 1083 SchemeManager schemeView(state, SchemeManager::View, config); 1084 SchemeManager schemeWindow(state, SchemeManager::Window, config); 1085 SchemeManager schemeButton(state, SchemeManager::Button, config); 1086 SchemeManager schemeSelection(state, SchemeManager::Selection, config); 1087 1088 palette.setBrush(state, QPalette::WindowText, schemeWindow.foreground()); 1089 palette.setBrush(state, QPalette::Window, schemeWindow.background()); 1090 palette.setBrush(state, QPalette::Base, schemeView.background()); 1091 palette.setBrush(state, QPalette::Text, schemeView.foreground()); 1092 palette.setBrush(state, QPalette::Button, schemeButton.background()); 1093 palette.setBrush(state, QPalette::ButtonText, schemeButton.foreground()); 1094 palette.setBrush(state, QPalette::Highlight, schemeSelection.background()); 1095 palette.setBrush(state, QPalette::HighlightedText, schemeSelection.foreground()); 1096 palette.setBrush(state, QPalette::ToolTipBase, schemeTooltip.background()); 1097 palette.setBrush(state, QPalette::ToolTipText, schemeTooltip.foreground()); 1098 1099 palette.setColor(state, QPalette::Light, schemeWindow.shade(SchemeManager::LightShade)); 1100 palette.setColor(state, QPalette::Midlight, schemeWindow.shade(SchemeManager::MidlightShade)); 1101 palette.setColor(state, QPalette::Mid, schemeWindow.shade(SchemeManager::MidShade)); 1102 palette.setColor(state, QPalette::Dark, schemeWindow.shade(SchemeManager::DarkShade)); 1103 palette.setColor(state, QPalette::Shadow, schemeWindow.shade(SchemeManager::ShadowShade)); 1104 1105 palette.setBrush(state, QPalette::AlternateBase, schemeView.background(SchemeManager::AlternateBackground)); 1106 palette.setBrush(state, QPalette::Link, schemeView.foreground(SchemeManager::LinkText)); 1107 palette.setBrush(state, QPalette::LinkVisited, schemeView.foreground(SchemeManager::VisitedText)); 1108 } 1109 1110 return palette; 1111 } 1112