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