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
21class HCYColorSpace
22{
23public:
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
32public:
33
34 qreal h;
35 qreal c;
36 qreal y;
37 qreal a;
38
39private:
40
41 static qreal gamma(qreal);
42 static qreal igamma(qreal);
43 static qreal lumag(qreal, qreal, qreal);
44};
45
46// ------------------------------------------------------------------------------
47
48namespace ColorTools
49{
50
51static 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*/
62static inline qreal normalize(qreal a)
63{
64 return (a < 1.0 ? (a > 0.0 ? a : 0.0) : 1.0);
65}
66
67static 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 */
78qreal 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 */
86void 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 */
114static 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
124qreal 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 */
132QColor 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 */
144QColor 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 */
157QColor 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 */
169QColor 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
194static 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 */
214QColor 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 */
264QColor 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
287static const qreal yc[3] = {0.299, 0.587, 0.114 };
288#elif HCY_REC == 709
289static const qreal yc[3] = {0.2126, 0.7152, 0.0722 };
290#else // use Qt values
291static const qreal yc[3] = {0.34375, 0.5, 0.15625};
292#endif
293
294qreal HCYColorSpace::gamma(qreal n)
295{
296 return pow(ColorTools::normalize(n), 2.2);
297}
298
299qreal HCYColorSpace::igamma(qreal n)
300{
301 return pow(ColorTools::normalize(n), 1.0 / 2.2);
302}
303
304qreal HCYColorSpace::lumag(qreal r, qreal g, qreal b)
305{
306 return r * yc[0] + g * yc[1] + b * yc[2];
307}
308
309HCYColorSpace::HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_)
310{
311 h = h_;
312 c = c_;
313 y = y_;
314 a = a_;
315}
316
317HCYColorSpace::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
360QColor 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
444qreal HCYColorSpace::luma(const QColor& color)
445{
446 return lumag(gamma(color.redF()),
447 gamma(color.greenF()),
448 gamma(color.blueF()));
449}
450
451// -------------------------------------------------------------------------------------
452
453class StateEffects
454{
455public:
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
463private:
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
487private:
488
489 int _effects[3];
490 double _amount[3];
491 QColor _color;
492};
493
494StateEffects::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
535QBrush 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
568QBrush 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
592struct 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
606struct DecoDefaultColors
607{
608 int Hover[3];
609 int Focus[3];
610};
611
612// these numbers come from the Breeze color scheme
613static 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
627static 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
641static 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
655static 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
669static 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
683static 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
697static const DecoDefaultColors defaultDecorationColors =
698{
699 { 147, 206, 233 }, // Hover
700 { 61, 174, 233 }, // Focus
701};
702
703// ------------------------------------------------------------------------------------
704
705class SchemeManagerPrivate : public QSharedData
706{
707public:
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
718private:
719
720 void init(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors);
721
722private:
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
738SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config,
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
754SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config,
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
775void SchemeManagerPrivate::init(const KSharedConfigPtr& config,
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
820QBrush 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
843QBrush 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
866QBrush 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
877qreal SchemeManagerPrivate::contrast() const
878{
879 return _contrast;
880}
881
882// ------------------------------------------------------------------------------------
883
885{
886}
887
888SchemeManager::~SchemeManager()
889{
890}
891
893{
894 d = other.d;
895 return *this;
896}
897
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
954qreal 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
991QColor 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
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
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
QString readEntry(const char *key, const char *aDefault=nullptr) const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
A set of methods used to work with colors.
ColorSet
This enumeration describes the color set for which a color is being selected.
@ Selection
Selected items in views.
@ Complementary
Complementary areas.
@ View
Views; for example, frames, input fields, etc.
@ Button
Buttons and button-like controls.
@ Window
Non-editable window elements; for example, menus.
@ Tooltip
Tooltips.
QBrush background(BackgroundRole=NormalBackground) const
Retrieve the requested background brush.
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 ...
static qreal contrastF(const KSharedConfigPtr &config=KSharedConfigPtr())
Returns the contrast for borders as a floating point value.
BackgroundRole
This enumeration describes the background color being selected from the given set.
@ LinkBackground
Fourth color; corresponds to (unvisited) links.
@ PositiveBackground
Eigth color; for example, success messages, trusted content.
@ AlternateBackground
Alternate background; for example, for use in lists.
@ ActiveBackground
Third color; for example, items which are new, active, requesting attention, etc.
@ VisitedBackground
Fifth color; corresponds to visited links.
@ NegativeBackground
Sixth color; for example, errors, untrusted content, etc.
@ NeutralBackground
Seventh color; for example, warnings, secure/encrypted content.
ForegroundRole
This enumeration describes the foreground color being selected from the given set.
@ InactiveText
Second color; for example, comments, items which are old, inactive or disabled.
@ LinkText
Fourth color; use for (unvisited) links.
@ VisitedText
Fifth color; used for (visited) links.
@ ActiveText
Third color; for example items which are new, active, requesting attention, etc.
@ NegativeText
Sixth color; for example, errors, untrusted content, deletions, etc.
@ NeutralText
Seventh color; for example, warnings, secure/encrypted content.
@ PositiveText
Eigth color; for example, additions, success messages, trusted content.
SchemeManager & operator=(const SchemeManager &)
Standard assignment operator.
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 ...
static int contrast()
Returns the contrast for borders.
DecorationRole
This enumeration describes the decoration color being selected from the given set.
@ FocusColor
Color used to draw decorations for items which have input focus.
static QPalette createApplicationPalette(const KSharedConfigPtr &config)
Used to obtain the QPalette that will be used to set the application palette from KDE Platform theme.
QColor shade(ShadeRole) const
Retrieve the requested shade color, using SchemeManager::background(SchemeManager::NormalBackground) ...
QBrush decoration(DecorationRole) const
Retrieve the requested decoration brush.
ShadeRole
This enumeration describes the color shade being selected from the given set.
@ DarkShade
The dark color is in between mid() and shadow().
@ MidlightShade
The midlight color is in between base() and light().
@ LightShade
The light color is lighter than dark() or shadow() and contrasts with the base color.
@ ShadowShade
The shadow color is darker than light() or midlight() and contrasts the base color.
@ MidShade
The mid color is in between base() and dark().
QBrush foreground(ForegroundRole=NormalText) const
Retrieve the requested foreground brush.
SchemeManager(const SchemeManager &)
Construct a copy of another SchemeManager.
KGUIADDONS_EXPORT QColor darken(const QColor &, qreal amount=0.5, qreal chromaGain=1.0)
KGUIADDONS_EXPORT QColor overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp=QPainter::CompositionMode_SourceOver)
KGUIADDONS_EXPORT void getHcy(const QColor &, qreal *hue, qreal *chroma, qreal *luma, qreal *alpha=nullptr)
KGUIADDONS_EXPORT qreal luma(const QColor &)
KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount=0.0)
KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias=0.5)
KGUIADDONS_EXPORT QColor tint(const QColor &base, const QColor &color, qreal amount=0.3)
KGUIADDONS_EXPORT QColor lighten(const QColor &, qreal amount=0.5, qreal chromaInverseGain=1.0)
KGUIADDONS_EXPORT qreal contrastRatio(const QColor &, const QColor &)
QString normalize(QStringView str)
const QColor & color() const const
float alphaF() const const
float blueF() const const
QColor fromRgbF(float r, float g, float b, float a)
float greenF() const const
float redF() const const
void setAlpha(int alpha)
Format_ARGB32_Premultiplied
void setBrush(ColorGroup group, ColorRole role, const QBrush &brush)
void setColor(ColorGroup group, ColorRole role, const QColor &color)
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:40 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.