Kstars

schememanager.cpp
1 /*
2  This file is a part of digiKam project
3  http://www.digikam.org
4 
5  SPDX-FileCopyrightText: 2006-2018 Gilles Caulier <caulier dot gilles at gmail dot com>
6  SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad at users dot sourceforge dot net>
7 
8  SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "schememanager.h"
12 
13 #include <kconfig.h>
14 #include <kconfiggroup.h>
15 
16 #include <QWidget>
17 #include <QPainter>
18 
19 #include <cmath>
20 
21 class HCYColorSpace
22 {
23 public:
24 
25  explicit HCYColorSpace(const QColor&);
26  explicit HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0);
27 
28  QColor qColor() const;
29 
30  static qreal luma(const QColor &);
31 
32 public:
33 
34  qreal h;
35  qreal c;
36  qreal y;
37  qreal a;
38 
39 private:
40 
41  static qreal gamma(qreal);
42  static qreal igamma(qreal);
43  static qreal lumag(qreal, qreal, qreal);
44 };
45 
46 // ------------------------------------------------------------------------------
47 
48 namespace ColorTools
49 {
50 
51 static inline qreal wrap(qreal a, qreal d = 1.0)
52 {
53  qreal r = fmod(a, d);
54 
55  return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0));
56 }
57 
58 /*
59  normalize: like qBound(a, 0.0, 1.0) but without needing the args and with
60  "safer" behavior on NaN (isnan(a) -> return 0.0)
61 */
62 static inline qreal normalize(qreal a)
63 {
64  return (a < 1.0 ? (a > 0.0 ? a : 0.0) : 1.0);
65 }
66 
67 static inline qreal mixQreal(qreal a, qreal b, qreal bias)
68 {
69  return (a + (b - a) * bias);
70 }
71 
72 /**
73  * Calculate the luma of a color. Luma is weighted sum of gamma-adjusted
74  * R'G'B' components of a color. The result is similar to qGray. The range
75  * is from 0.0 (black) to 1.0 (white).
76  *
77  */
78 qreal luma(const QColor& color)
79 {
80  return HCYColorSpace::luma(color);
81 }
82 
83 /**
84  * Calculate hue, chroma and luma of a color in one call.
85  */
86 void getHcy(const QColor& color, qreal* h, qreal* c, qreal* y, qreal* a = 0)
87 {
88  if (!c || !h || !y)
89  {
90  return;
91  }
92 
93  HCYColorSpace khcy(color);
94  *c = khcy.c;
95  *h = khcy.h;
96  *y = khcy.y;
97 
98  if (a)
99  {
100  *a = khcy.a;
101  }
102 }
103 
104 /**
105  * Calculate the contrast ratio between two colors, according to the
106  * W3C/WCAG2.0 algorithm, (Lmax + 0.05)/(Lmin + 0.05), where Lmax and Lmin
107  * are the luma values of the lighter color and the darker color,
108  * respectively.
109  *
110  * A contrast ration of 5:1 (result == 5.0) is the minimum for "normal"
111  * text to be considered readable (large text can go as low as 3:1). The
112  * ratio ranges from 1:1 (result == 1.0) to 21:1 (result == 21.0).
113  */
114 static qreal contrastRatioForLuma(qreal y1, qreal y2)
115 {
116  if (y1 > y2)
117  {
118  return (y1 + 0.05) / (y2 + 0.05);
119  }
120 
121  return (y2 + 0.05) / (y1 + 0.05);
122 }
123 
124 qreal contrastRatio(const QColor& c1, const QColor& c2)
125 {
126  return contrastRatioForLuma(luma(c1), luma(c2));
127 }
128 
129 /**
130  * Adjust the luma of a color by changing its distance from white.
131  */
132 QColor lighten(const QColor& color, qreal ky = 0.5, qreal kc = 1.0)
133 {
134  HCYColorSpace c(color);
135  c.y = 1.0 - ColorTools::normalize((1.0 - c.y) * (1.0 - ky));
136  c.c = 1.0 - ColorTools::normalize((1.0 - c.c) * kc);
137 
138  return c.qColor();
139 }
140 
141 /**
142  * Adjust the luma of a color by changing its distance from black.
143  */
144 QColor darken(const QColor& color, qreal ky = 0.5, qreal kc = 1.0)
145 {
146  HCYColorSpace c(color);
147  c.y = ColorTools::normalize(c.y * (1.0 - ky));
148  c.c = ColorTools::normalize(c.c * kc);
149 
150  return c.qColor();
151 }
152 
153 /**
154  * Adjust the luma and chroma components of a color. The amount is added
155  * to the corresponding component.
156  */
157 QColor shade(const QColor& color, qreal ky, qreal kc = 0.0)
158 {
159  HCYColorSpace c(color);
160  c.y = ColorTools::normalize(c.y + ky);
161  c.c = ColorTools::normalize(c.c + kc);
162 
163  return c.qColor();
164 }
165 
166 /**
167  * Blend two colors into a new color by linear combination.
168  */
169 QColor mix(const QColor& c1, const QColor& c2, qreal bias)
170 {
171  if (bias <= 0.0)
172  {
173  return c1;
174  }
175 
176  if (bias >= 1.0)
177  {
178  return c2;
179  }
180 
181  if (qIsNaN(bias))
182  {
183  return c1;
184  }
185 
186  qreal r = mixQreal(c1.redF(), c2.redF(), bias);
187  qreal g = mixQreal(c1.greenF(), c2.greenF(), bias);
188  qreal b = mixQreal(c1.blueF(), c2.blueF(), bias);
189  qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias);
190 
191  return QColor::fromRgbF(r, g, b, a);
192 }
193 
194 static QColor tintHelper(const QColor& base, qreal baseLuma, const QColor& color, qreal amount)
195 {
196  HCYColorSpace result(mix(base, color, pow(amount, 0.3)));
197  result.y = mixQreal(baseLuma, result.y, amount);
198 
199  return result.qColor();
200 }
201 
202 /**
203  * Create a new color by tinting one color with another. This function is
204  * meant for creating additional colors withings the same class (background,
205  * foreground) from colors in a different class. Therefore when @p amount
206  * is low, the luma of @p base is mostly preserved, while the hue and
207  * chroma of @p color is mostly inherited.
208  *
209  * @param base color to be tinted
210  * @param color color with which to tint
211  * @param amount how strongly to tint the base; 0.0 gives @p base,
212  * 1.0 gives @p color
213  */
214 QColor tint(const QColor& base, const QColor& color, qreal amount = 0.3)
215 {
216  if (amount <= 0.0)
217  {
218  return base;
219  }
220 
221  if (amount >= 1.0)
222  {
223  return color;
224  }
225 
226  if (qIsNaN(amount))
227  {
228  return base;
229  }
230 
231  qreal baseLuma = luma(base); //cache value because luma call is expensive
232  double ri = contrastRatioForLuma(baseLuma, luma(color));
233  double rg = 1.0 + ((ri + 1.0) * amount * amount * amount);
234  double u = 1.0, l = 0.0;
235  QColor result;
236 
237  for (int i = 12; i; --i)
238  {
239  double a = 0.5 * (l + u);
240  result = tintHelper(base, baseLuma, color, a);
241  double ra = contrastRatioForLuma(baseLuma, luma(result));
242 
243  if (ra > rg)
244  {
245  u = a;
246  }
247  else
248  {
249  l = a;
250  }
251  }
252 
253  return result;
254 }
255 
256 /**
257  * Blend two colors into a new color by painting the second color over the
258  * first using the specified composition mode.
259  *
260  * @param base the base color (alpha channel is ignored).
261  * @param paint the color to be overlayed onto the base color.
262  * @param comp the CompositionMode used to do the blending.
263  */
264 QColor overlayColors(const QColor& base, const QColor& paint,
266 {
267  // This isn't the fastest way, but should be "fast enough".
268  // It's also the only safe way to use QPainter::CompositionMode
270  QPainter p(&img);
271  QColor start = base;
272  start.setAlpha(255); // opaque
273  p.fillRect(0, 0, 1, 1, start);
274  p.setCompositionMode(comp);
275  p.fillRect(0, 0, 1, 1, paint);
276  p.end();
277 
278  return img.pixel(0, 0);
279 }
280 
281 } // namespace ColorTools
282 
283 // ------------------------------------------------------------------------------
284 
285 #define HCY_REC 709 // use 709 for now
286 #if HCY_REC == 601
287 static const qreal yc[3] = {0.299, 0.587, 0.114 };
288 #elif HCY_REC == 709
289 static const qreal yc[3] = {0.2126, 0.7152, 0.0722 };
290 #else // use Qt values
291 static const qreal yc[3] = {0.34375, 0.5, 0.15625};
292 #endif
293 
294 qreal HCYColorSpace::gamma(qreal n)
295 {
296  return pow(ColorTools::normalize(n), 2.2);
297 }
298 
299 qreal HCYColorSpace::igamma(qreal n)
300 {
301  return pow(ColorTools::normalize(n), 1.0 / 2.2);
302 }
303 
304 qreal HCYColorSpace::lumag(qreal r, qreal g, qreal b)
305 {
306  return r * yc[0] + g * yc[1] + b * yc[2];
307 }
308 
309 HCYColorSpace::HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_)
310 {
311  h = h_;
312  c = c_;
313  y = y_;
314  a = a_;
315 }
316 
317 HCYColorSpace::HCYColorSpace(const QColor& color)
318 {
319  qreal r = gamma(color.redF());
320  qreal g = gamma(color.greenF());
321  qreal b = gamma(color.blueF());
322  a = color.alphaF();
323 
324  // luma component
325  y = lumag(r, g, b);
326 
327  // hue component
328  qreal p = qMax(qMax(r, g), b);
329  qreal n = qMin(qMin(r, g), b);
330  qreal d = 6.0 * (p - n);
331 
332  if (n == p)
333  {
334  h = 0.0;
335  }
336  else if (r == p)
337  {
338  h = ((g - b) / d);
339  }
340  else if (g == p)
341  {
342  h = ((b - r) / d) + (1.0 / 3.0);
343  }
344  else
345  {
346  h = ((r - g) / d) + (2.0 / 3.0);
347  }
348 
349  // chroma component
350  if (r == g && g == b)
351  {
352  c = 0.0;
353  }
354  else
355  {
356  c = qMax((y - n) / y, (p - y) / (1 - y));
357  }
358 }
359 
360 QColor HCYColorSpace::qColor() const
361 {
362  // start with sane component values
363  qreal _h = ColorTools::wrap(h);
364  qreal _c = ColorTools::normalize(c);
365  qreal _y = ColorTools::normalize(y);
366 
367  // calculate some needed variables
368  qreal _hs = _h * 6.0, th, tm;
369 
370  if (_hs < 1.0)
371  {
372  th = _hs;
373  tm = yc[0] + yc[1] * th;
374  }
375  else if (_hs < 2.0)
376  {
377  th = 2.0 - _hs;
378  tm = yc[1] + yc[0] * th;
379  }
380  else if (_hs < 3.0)
381  {
382  th = _hs - 2.0;
383  tm = yc[1] + yc[2] * th;
384  }
385  else if (_hs < 4.0)
386  {
387  th = 4.0 - _hs;
388  tm = yc[2] + yc[1] * th;
389  }
390  else if (_hs < 5.0)
391  {
392  th = _hs - 4.0;
393  tm = yc[2] + yc[0] * th;
394  }
395  else
396  {
397  th = 6.0 - _hs;
398  tm = yc[0] + yc[2] * th;
399  }
400 
401  // calculate RGB channels in sorted order
402  qreal tn, to, tp;
403 
404  if (tm >= _y)
405  {
406  tp = _y + _y * _c * (1.0 - tm) / tm;
407  to = _y + _y * _c * (th - tm) / tm;
408  tn = _y - (_y * _c);
409  }
410  else
411  {
412  tp = _y + (1.0 - _y) * _c;
413  to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm);
414  tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm);
415  }
416 
417  // return RGB channels in appropriate order
418  if (_hs < 1.0)
419  {
420  return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a);
421  }
422  else if (_hs < 2.0)
423  {
424  return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a);
425  }
426  else if (_hs < 3.0)
427  {
428  return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a);
429  }
430  else if (_hs < 4.0)
431  {
432  return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a);
433  }
434  else if (_hs < 5.0)
435  {
436  return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a);
437  }
438  else
439  {
440  return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a);
441  }
442 }
443 
444 qreal HCYColorSpace::luma(const QColor& color)
445 {
446  return lumag(gamma(color.redF()),
447  gamma(color.greenF()),
448  gamma(color.blueF()));
449 }
450 
451 // -------------------------------------------------------------------------------------
452 
453 class StateEffects
454 {
455 public:
456 
457  explicit StateEffects(QPalette::ColorGroup state, const KSharedConfigPtr&);
458  ~StateEffects() = default;
459 
460  QBrush brush(const QBrush& background) const;
461  QBrush brush(const QBrush& foreground, const QBrush& background) const;
462 
463 private:
464 
465  enum Effects
466  {
467  // Effects
468  Intensity = 0,
469  Color = 1,
470  Contrast = 2,
471  // Intensity
472  IntensityNoEffect = 0,
473  IntensityShade = 1,
474  IntensityDarken = 2,
475  IntensityLighten = 3,
476  // Color
477  ColorNoEffect = 0,
478  ColorDesaturate = 1,
479  ColorFade = 2,
480  ColorTint = 3,
481  // Contrast
482  ContrastNoEffect = 0,
483  ContrastFade = 1,
484  ContrastTint = 2
485  };
486 
487 private:
488 
489  int _effects[3];
490  double _amount[3];
491  QColor _color;
492 };
493 
494 StateEffects::StateEffects(QPalette::ColorGroup state, const KSharedConfigPtr& config)
495  : _color(0, 0, 0, 0)
496 {
497  QString group;
498 
499  if (state == QPalette::Disabled)
500  {
501  group = QLatin1String("ColorEffects:Disabled");
502  }
503  else if (state == QPalette::Inactive)
504  {
505  group = QLatin1String("ColorEffects:Inactive");
506  }
507 
508  _effects[0] = 0;
509  _effects[1] = 0;
510  _effects[2] = 0;
511 
512  if (!group.isEmpty())
513  {
514  KConfigGroup cfg(config, group);
515  const bool enabledByDefault = (state == QPalette::Disabled);
516 
517  if (cfg.readEntry("Enable", enabledByDefault))
518  {
519  _effects[Intensity] = cfg.readEntry("IntensityEffect", (int)((state == QPalette::Disabled) ? IntensityDarken : IntensityNoEffect));
520  _effects[Color] = cfg.readEntry("ColorEffect", (int)((state == QPalette::Disabled) ? ColorNoEffect : ColorDesaturate));
521  _effects[Contrast] = cfg.readEntry("ContrastEffect", (int)((state == QPalette::Disabled) ? ContrastFade : ContrastTint));
522  _amount[Intensity] = cfg.readEntry("IntensityAmount", (state == QPalette::Disabled) ? 0.10 : 0.0);
523  _amount[Color] = cfg.readEntry("ColorAmount", (state == QPalette::Disabled) ? 0.0 : -0.9);
524  _amount[Contrast] = cfg.readEntry("ContrastAmount", (state == QPalette::Disabled) ? 0.65 : 0.25);
525 
526  if (_effects[Color] > ColorNoEffect)
527  {
528  _color = cfg.readEntry("Color", (state == QPalette::Disabled) ? QColor(56, 56, 56)
529  : QColor(112, 111, 110));
530  }
531  }
532  }
533 }
534 
535 QBrush StateEffects::brush(const QBrush& background) const
536 {
537  QColor color = background.color(); // TODO - actually work on brushes
538 
539  switch (_effects[Intensity])
540  {
541  case IntensityShade:
542  color = ColorTools::shade(color, _amount[Intensity]);
543  break;
544  case IntensityDarken:
545  color = ColorTools::darken(color, _amount[Intensity]);
546  break;
547  case IntensityLighten:
548  color = ColorTools::lighten(color, _amount[Intensity]);
549  break;
550  }
551 
552  switch (_effects[Color])
553  {
554  case ColorDesaturate:
555  color = ColorTools::darken(color, 0.0, 1.0 - _amount[Color]);
556  break;
557  case ColorFade:
558  color = ColorTools::mix(color, _color, _amount[Color]);
559  break;
560  case ColorTint:
561  color = ColorTools::tint(color, _color, _amount[Color]);
562  break;
563  }
564 
565  return QBrush(color);
566 }
567 
568 QBrush StateEffects::brush(const QBrush& foreground, const QBrush& background) const
569 {
570  QColor color = foreground.color();
571  QColor bg = background.color();
572 
573  // Apply the foreground effects
574 
575  switch (_effects[Contrast])
576  {
577  case ContrastFade:
578  color = ColorTools::mix(color, bg, _amount[Contrast]);
579  break;
580  case ContrastTint:
581  color = ColorTools::tint(color, bg, _amount[Contrast]);
582  break;
583  }
584 
585  // Now apply global effects
586 
587  return brush(color);
588 }
589 
590 // ------------------------------------------------------------------------------------
591 
592 struct SetDefaultColors
593 {
594  int NormalBackground[3];
595  int AlternateBackground[3];
596  int NormalText[3];
597  int InactiveText[3];
598  int ActiveText[3];
599  int LinkText[3];
600  int VisitedText[3];
601  int NegativeText[3];
602  int NeutralText[3];
603  int PositiveText[3];
604 };
605 
606 struct DecoDefaultColors
607 {
608  int Hover[3];
609  int Focus[3];
610 };
611 
612 // these numbers come from the Breeze color scheme
613 static const SetDefaultColors defaultViewColors =
614 {
615  { 252, 252, 252 }, // Background
616  { 239, 240, 241 }, // Alternate
617  { 49, 54, 59 }, // Normal
618  { 127, 140, 141 }, // Inactive
619  { 61, 174, 233 }, // Active
620  { 41, 128, 185 }, // Link
621  { 127, 140, 141 }, // Visited
622  { 218, 68, 83 }, // Negative
623  { 246, 116, 0 }, // Neutral
624  { 39, 174, 96 } // Positive
625 };
626 
627 static const SetDefaultColors defaultWindowColors =
628 {
629  { 239, 240, 241 }, // Background
630  { 189, 195, 199 }, // Alternate
631  { 49, 54, 59 }, // Normal
632  { 127, 140, 141 }, // Inactive
633  { 61, 174, 233 }, // Active
634  { 41, 128, 185 }, // Link
635  { 127, 140, 141 }, // Visited
636  { 218, 68, 83 }, // Negative
637  { 246, 116, 0 }, // Neutral
638  { 39, 174, 96 } // Positive
639 };
640 
641 static const SetDefaultColors defaultButtonColors =
642 {
643  { 239, 240, 241 }, // Background
644  { 189, 195, 199 }, // Alternate
645  { 49, 54, 59 }, // Normal
646  { 127, 140, 141 }, // Inactive
647  { 61, 174, 233 }, // Active
648  { 41, 128, 185 }, // Link
649  { 127, 140, 141 }, // Visited
650  { 218, 68, 83 }, // Negative
651  { 246, 116, 0 }, // Neutral
652  { 39, 174, 96 } // Positive
653 };
654 
655 static const SetDefaultColors defaultSelectionColors =
656 {
657  { 61, 174, 233 }, // Background
658  { 29, 153, 243 }, // Alternate
659  { 239, 240, 241 }, // Normal
660  { 239, 240, 241 }, // Inactive
661  { 252, 252, 252 }, // Active
662  { 253, 188, 75 }, // Link
663  { 189, 195, 199 }, // Visited
664  { 218, 68, 83 }, // Negative
665  { 246, 116, 0 }, // Neutral
666  { 39, 174, 96 } // Positive
667 };
668 
669 static const SetDefaultColors defaultTooltipColors =
670 {
671  { 49, 54, 59 }, // Background
672  { 77, 77, 77 }, // Alternate
673  { 239, 240, 241 }, // Normal
674  { 189, 195, 199 }, // Inactive
675  { 61, 174, 233 }, // Active
676  { 41, 128, 185 }, // Link
677  { 127, 140, 141 }, // Visited
678  { 218, 68, 83 }, // Negative
679  { 246, 116, 0 }, // Neutral
680  { 39, 174, 96 } // Positive
681 };
682 
683 static const SetDefaultColors defaultComplementaryColors =
684 {
685  { 49, 54, 59 }, // Background
686  { 77, 77, 77 }, // Alternate
687  { 239, 240, 241 }, // Normal
688  { 189, 195, 199 }, // Inactive
689  { 61, 174, 233 }, // Active
690  { 41, 128, 185 }, // Link
691  { 127, 140, 141 }, // Visited
692  { 218, 68, 83 }, // Negative
693  { 246, 116, 0 }, // Neutral
694  { 39, 174, 96 } // Positive
695 };
696 
697 static const DecoDefaultColors defaultDecorationColors =
698 {
699  { 147, 206, 233 }, // Hover
700  { 61, 174, 233 }, // Focus
701 };
702 
703 // ------------------------------------------------------------------------------------
704 
705 class SchemeManagerPrivate : public QSharedData
706 {
707 public:
708 
709  explicit SchemeManagerPrivate(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors);
710  explicit SchemeManagerPrivate(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors, const QBrush&);
711  ~SchemeManagerPrivate() = default;
712 
713  QBrush background(SchemeManager::BackgroundRole) const;
714  QBrush foreground(SchemeManager::ForegroundRole) const;
715  QBrush decoration(SchemeManager::DecorationRole) const;
716  qreal contrast() const;
717 
718 private:
719 
720  void init(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors);
721 
722 private:
723 
724  struct
725  {
726  QBrush fg[8],
727  bg[8],
728  deco[2];
729  } _brushes;
730 
731  qreal _contrast;
732 };
733 
734 #define DEFAULT(c) QColor( c[0], c[1], c[2] )
735 #define SET_DEFAULT(a) DEFAULT( defaults.a )
736 #define DECO_DEFAULT(a) DEFAULT( defaultDecorationColors.a )
737 
738 SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config,
739  QPalette::ColorGroup state,
740  const char* group,
741  SetDefaultColors defaults)
742 {
743  KConfigGroup cfg(config, group);
744  _contrast = SchemeManager::contrastF(config);
745 
746  // loaded-from-config colors (no adjustment)
747  _brushes.bg[0] = cfg.readEntry("BackgroundNormal", SET_DEFAULT(NormalBackground));
748  _brushes.bg[1] = cfg.readEntry("BackgroundAlternate", SET_DEFAULT(AlternateBackground));
749 
750  // the rest
751  init(config, state, group, defaults);
752 }
753 
754 SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config,
755  QPalette::ColorGroup state,
756  const char* group,
757  SetDefaultColors defaults,
758  const QBrush& tint)
759 {
760  KConfigGroup cfg(config, group);
761  _contrast = SchemeManager::contrastF(config);
762 
763  // loaded-from-config colors
764  _brushes.bg[0] = cfg.readEntry("BackgroundNormal", SET_DEFAULT(NormalBackground));
765  _brushes.bg[1] = cfg.readEntry("BackgroundAlternate", SET_DEFAULT(AlternateBackground));
766 
767  // adjustment
768  _brushes.bg[0] = ColorTools::tint(_brushes.bg[0].color(), tint.color(), 0.4);
769  _brushes.bg[1] = ColorTools::tint(_brushes.bg[1].color(), tint.color(), 0.4);
770 
771  // the rest
772  init(config, state, group, defaults);
773 }
774 
775 void SchemeManagerPrivate::init(const KSharedConfigPtr& config,
776  QPalette::ColorGroup state,
777  const char* group,
778  SetDefaultColors defaults)
779 {
780  KConfigGroup cfg(config, group);
781 
782  // loaded-from-config colors
783  _brushes.fg[0] = cfg.readEntry("ForegroundNormal", SET_DEFAULT(NormalText));
784  _brushes.fg[1] = cfg.readEntry("ForegroundInactive", SET_DEFAULT(InactiveText));
785  _brushes.fg[2] = cfg.readEntry("ForegroundActive", SET_DEFAULT(ActiveText));
786  _brushes.fg[3] = cfg.readEntry("ForegroundLink", SET_DEFAULT(LinkText));
787  _brushes.fg[4] = cfg.readEntry("ForegroundVisited", SET_DEFAULT(VisitedText));
788  _brushes.fg[5] = cfg.readEntry("ForegroundNegative", SET_DEFAULT(NegativeText));
789  _brushes.fg[6] = cfg.readEntry("ForegroundNeutral", SET_DEFAULT(NeutralText));
790  _brushes.fg[7] = cfg.readEntry("ForegroundPositive", SET_DEFAULT(PositiveText));
791  _brushes.deco[0] = cfg.readEntry("DecorationHover", DECO_DEFAULT(Hover));
792  _brushes.deco[1] = cfg.readEntry("DecorationFocus", DECO_DEFAULT(Focus));
793 
794  // apply state adjustments
795 
796  if (state != QPalette::Active)
797  {
798  StateEffects effects(state, config);
799 
800  for (auto &brush : _brushes.fg)
801  {
802  brush = effects.brush(brush, _brushes.bg[0]);
803  }
804 
805  _brushes.deco[0] = effects.brush(_brushes.deco[0], _brushes.bg[0]);
806  _brushes.deco[1] = effects.brush(_brushes.deco[1], _brushes.bg[0]);
807  _brushes.bg[0] = effects.brush(_brushes.bg[0]);
808  _brushes.bg[1] = effects.brush(_brushes.bg[1]);
809  }
810 
811  // calculated backgrounds
812  _brushes.bg[2] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[2].color());
813  _brushes.bg[3] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[3].color());
814  _brushes.bg[4] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[4].color());
815  _brushes.bg[5] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[5].color());
816  _brushes.bg[6] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[6].color());
817  _brushes.bg[7] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[7].color());
818 }
819 
820 QBrush SchemeManagerPrivate::background(SchemeManager::BackgroundRole role) const
821 {
822  switch (role)
823  {
825  return _brushes.bg[1];
827  return _brushes.bg[2];
829  return _brushes.bg[3];
831  return _brushes.bg[4];
833  return _brushes.bg[5];
835  return _brushes.bg[6];
837  return _brushes.bg[7];
838  default:
839  return _brushes.bg[0];
840  }
841 }
842 
843 QBrush SchemeManagerPrivate::foreground(SchemeManager::ForegroundRole role) const
844 {
845  switch (role)
846  {
848  return _brushes.fg[1];
850  return _brushes.fg[2];
852  return _brushes.fg[3];
854  return _brushes.fg[4];
856  return _brushes.fg[5];
858  return _brushes.fg[6];
860  return _brushes.fg[7];
861  default:
862  return _brushes.fg[0];
863  }
864 }
865 
866 QBrush SchemeManagerPrivate::decoration(SchemeManager::DecorationRole role) const
867 {
868  switch (role)
869  {
871  return _brushes.deco[1];
872  default:
873  return _brushes.deco[0];
874  }
875 }
876 
877 qreal SchemeManagerPrivate::contrast() const
878 {
879  return _contrast;
880 }
881 
882 // ------------------------------------------------------------------------------------
883 
884 SchemeManager::SchemeManager(const SchemeManager& other) : d(other.d)
885 {
886 }
887 
888 SchemeManager::~SchemeManager()
889 {
890 }
891 
893 {
894  d = other.d;
895  return *this;
896 }
897 
898 SchemeManager::SchemeManager(QPalette::ColorGroup state, ColorSet set, KSharedConfigPtr config)
899 {
900  if (!config)
901  {
902  config = KSharedConfig::openConfig();
903  }
904 
905  switch (set)
906  {
907  case Window:
908  d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors);
909  break;
910  case Button:
911  d = new SchemeManagerPrivate(config, state, "Colors:Button", defaultButtonColors);
912  break;
913  case Selection:
914  {
915  KConfigGroup group(config, "ColorEffects:Inactive");
916  // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
917  bool inactiveSelectionEffect = group.readEntry("ChangeSelectionColor", group.readEntry("Enable", true));
918  // if enabled, inactiver/disabled uses Window colors instead, ala gtk
919  // ...except tinted with the Selection:NormalBackground color so it looks more like selection
920  if (state == QPalette::Active || (state == QPalette::Inactive && !inactiveSelectionEffect))
921  {
922  d = new SchemeManagerPrivate(config, state, "Colors:Selection", defaultSelectionColors);
923  }
924  else if (state == QPalette::Inactive)
925  {
926  d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors,
928  }
929  else
930  {
931  // disabled (...and still want this branch when inactive+disabled exists)
932  d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors);
933  }
934  }
935  break;
936  case Tooltip:
937  d = new SchemeManagerPrivate(config, state, "Colors:Tooltip", defaultTooltipColors);
938  break;
939  case Complementary:
940  d = new SchemeManagerPrivate(config, state, "Colors:Complementary", defaultComplementaryColors);
941  break;
942  default:
943  d = new SchemeManagerPrivate(config, state, "Colors:View", defaultViewColors);
944  }
945 }
946 
948 {
950 
951  return g.readEntry("contrast", 7);
952 }
953 
954 qreal SchemeManager::contrastF(const KSharedConfigPtr& config)
955 {
956  if (config)
957  {
958  KConfigGroup g(config, "KDE");
959 
960  return 0.1 * g.readEntry("contrast", 7);
961  }
962 
963  return 0.1 * (qreal)contrast();
964 }
965 
967 {
968  return d->background(role);
969 }
970 
972 {
973  return d->foreground(role);
974 }
975 
977 {
978  return d->decoration(role);
979 }
980 
982 {
983  return shade(background().color(), role, d->contrast());
984 }
985 
987 {
988  return shade(color, role, SchemeManager::contrastF());
989 }
990 
991 QColor SchemeManager::shade(const QColor& color, ShadeRole role, qreal contrast, qreal chromaAdjust)
992 {
993  // nan -> 1.0
994  contrast = ((1.0 > contrast) ? ((-1.0 < contrast) ? contrast
995  : -1.0)
996  : 1.0);
997  qreal y = ColorTools::luma(color);
998  qreal yi = 1.0 - y;
999 
1000  // handle very dark colors (base, mid, dark, shadow == midlight, light)
1001  if (y < 0.006)
1002  {
1003  switch (role)
1004  {
1006  return ColorTools::shade(color, 0.05 + 0.95 * contrast, chromaAdjust);
1008  return ColorTools::shade(color, 0.01 + 0.20 * contrast, chromaAdjust);
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  {
1022  return ColorTools::shade(color, -0.02 - 0.20 * contrast, chromaAdjust);
1024  return ColorTools::shade(color, -0.06 - 0.60 * contrast, chromaAdjust);
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  {
1039  return ColorTools::shade(color, lightAmount, chromaAdjust);
1041  return ColorTools::shade(color, (0.15 + 0.35 * yi) * lightAmount, chromaAdjust);
1043  return ColorTools::shade(color, (0.35 + 0.15 * y) * darkAmount, chromaAdjust);
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 
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 
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  {
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 
1106  palette.setBrush(state, QPalette::Link, schemeView.foreground(SchemeManager::LinkText));
1108  }
1109 
1110  return palette;
1111 }
1112 
const QColor & color() const const
QString readEntry(const char *key, const char *aDefault=nullptr) const
qreal greenF() const const
KGUIADDONS_EXPORT qreal luma(const QColor &)
qreal redF() const const
Format_ARGB32_Premultiplied
@ NegativeText
Sixth color; for example, errors, untrusted content, deletions, etc.
@ Tooltip
Tooltips.
@ Button
Buttons and button-like controls.
Definition: schememanager.h:84
KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias=0.5)
QColor shade(ShadeRole) const
Retrieve the requested shade color, using SchemeManager::background(SchemeManager::NormalBackground) ...
qreal blueF() const const
BackgroundRole
This enumeration describes the background color being selected from the given set.
QBrush foreground(ForegroundRole=NormalText) const
Retrieve the requested foreground brush.
@ ShadowShade
The shadow color is darker than light() or midlight() and contrasts the base color.
KGUIADDONS_EXPORT QColor darken(const QColor &, qreal amount=0.5, qreal chromaGain=1.0)
@ ActiveText
Third color; for example items which are new, active, requesting attention, etc.
QString normalize(QStringView str)
@ PositiveBackground
Eigth color; for example, success messages, trusted content.
QColor fromRgbF(qreal r, qreal g, qreal b, qreal a)
@ LinkText
Fourth color; use for (unvisited) links.
KGUIADDONS_EXPORT QColor tint(const QColor &base, const QColor &color, qreal amount=0.3)
ForegroundRole
This enumeration describes the foreground color being selected from the given set.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
@ Complementary
Complementary areas.
static qreal contrastF(const KSharedConfigPtr &config=KSharedConfigPtr())
Returns the contrast for borders as a floating point value.
@ LinkBackground
Fourth color; corresponds to (unvisited) links.
@ LightShade
The light color is lighter than dark() or shadow() and contrasts with the base color.
ColorSet
This enumeration describes the color set for which a color is being selected.
Definition: schememanager.h:63
SchemeManager & operator=(const SchemeManager &)
Standard assignment operator.
KGUIADDONS_EXPORT qreal contrastRatio(const QColor &, const QColor &)
KGUIADDONS_EXPORT QColor lighten(const QColor &, qreal amount=0.5, qreal chromaInverseGain=1.0)
@ ActiveBackground
Third color; for example, items which are new, active, requesting attention, etc.
static void adjustForeground(QPalette &, ForegroundRole newRole=NormalText, QPalette::ColorRole color=QPalette::Text, ColorSet set=View, KSharedConfigPtr=KSharedConfigPtr())
Adjust a QPalette by replacing the specified QPalette::ColorRole with the requested foreground color ...
@ MidShade
The mid color is in between base() and dark().
bool isEmpty() const const
@ View
Views; for example, frames, input fields, etc.
Definition: schememanager.h:70
void setBrush(QPalette::ColorRole role, const QBrush &brush)
void setAlpha(int alpha)
QBrush decoration(DecorationRole) const
Retrieve the requested decoration brush.
void init(KXmlGuiWindow *window, KgDifficulty *difficulty=nullptr)
ShadeRole
This enumeration describes the color shade being selected from the given set.
@ AlternateBackground
Alternate background; for example, for use in lists.
static int contrast()
Returns the contrast for borders.
void setColor(QPalette::ColorGroup group, QPalette::ColorRole role, const QColor &color)
qreal alphaF() const const
@ NeutralText
Seventh color; for example, warnings, secure/encrypted content.
static void adjustBackground(QPalette &, BackgroundRole newRole=NormalBackground, QPalette::ColorRole color=QPalette::Base, ColorSet set=View, KSharedConfigPtr=KSharedConfigPtr())
Adjust a QPalette by replacing the specified QPalette::ColorRole with the requested background color ...
@ Window
Non-editable window elements; for example, menus.
Definition: schememanager.h:76
SchemeManager(const SchemeManager &)
Construct a copy of another SchemeManager.
@ NeutralBackground
Seventh color; for example, warnings, secure/encrypted content.
DecorationRole
This enumeration describes the decoration color being selected from the given set.
QBrush background(BackgroundRole=NormalBackground) const
Retrieve the requested background brush.
static QPalette createApplicationPalette(const KSharedConfigPtr &config)
Used to obtain the QPalette that will be used to set the application palette from KDE Platform theme.
@ PositiveText
Eigth color; for example, additions, success messages, trusted content.
@ VisitedBackground
Fifth color; corresponds to visited links.
@ Selection
Selected items in views.
Definition: schememanager.h:92
@ MidlightShade
The midlight color is in between base() and light().
KGUIADDONS_EXPORT QColor overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp=QPainter::CompositionMode_SourceOver)
@ InactiveText
Second color; for example, comments, items which are old, inactive or disabled.
KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount=0.0)
@ DarkShade
The dark color is in between mid() and shadow().
A set of methods used to work with colors.
Definition: schememanager.h:52
@ FocusColor
Color used to draw decorations for items which have input focus.
@ VisitedText
Fifth color; used for (visited) links.
KGUIADDONS_EXPORT void getHcy(const QColor &, qreal *hue, qreal *chroma, qreal *luma, qreal *alpha=nullptr)
@ NegativeBackground
Sixth color; for example, errors, untrusted content, etc.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:58 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.