File indexing completed on 2024-03-24 15:15:22

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