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