1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 1998 Jörg Habenicht <j.habenicht@europemail.com>
4     SPDX-FileCopyrightText: 2010 Christoph Feck <cfeck@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "kled.h"
10 
11 #include <QImage>
12 #include <QPainter>
13 #include <QStyle>
14 #include <QStyleOption>
15 
16 class KLedPrivate
17 {
18 public:
19     int darkFactor = 300;
20     QColor color;
21     KLed::State state = KLed::On;
22     KLed::Look look = KLed::Raised;
23     KLed::Shape shape = KLed::Circular;
24 
25     QPixmap cachedPixmap[2]; // for both states
26 };
27 
KLed(QWidget * parent)28 KLed::KLed(QWidget *parent)
29     : QWidget(parent)
30     , d(new KLedPrivate)
31 {
32     setColor(Qt::green);
33     updateAccessibleName();
34 }
35 
KLed(const QColor & color,QWidget * parent)36 KLed::KLed(const QColor &color, QWidget *parent)
37     : QWidget(parent)
38     , d(new KLedPrivate)
39 {
40     setColor(color);
41     updateAccessibleName();
42 }
43 
KLed(const QColor & color,State state,Look look,Shape shape,QWidget * parent)44 KLed::KLed(const QColor &color, State state, Look look, Shape shape, QWidget *parent)
45     : QWidget(parent)
46     , d(new KLedPrivate)
47 {
48     d->state = (state == Off ? Off : On);
49     d->look = look;
50     d->shape = shape;
51 
52     setColor(color);
53     updateAccessibleName();
54 }
55 
56 KLed::~KLed() = default;
57 
state() const58 KLed::State KLed::state() const
59 {
60     return d->state;
61 }
62 
shape() const63 KLed::Shape KLed::shape() const
64 {
65     return d->shape;
66 }
67 
color() const68 QColor KLed::color() const
69 {
70     return d->color;
71 }
72 
look() const73 KLed::Look KLed::look() const
74 {
75     return d->look;
76 }
77 
setState(State state)78 void KLed::setState(State state)
79 {
80     if (d->state == state) {
81         return;
82     }
83 
84     d->state = (state == Off ? Off : On);
85     updateCachedPixmap();
86     updateAccessibleName();
87 }
88 
setShape(Shape shape)89 void KLed::setShape(Shape shape)
90 {
91     if (d->shape == shape) {
92         return;
93     }
94 
95     d->shape = shape;
96     updateCachedPixmap();
97 }
98 
setColor(const QColor & color)99 void KLed::setColor(const QColor &color)
100 {
101     if (d->color == color) {
102         return;
103     }
104 
105     d->color = color;
106     updateCachedPixmap();
107 }
108 
setDarkFactor(int darkFactor)109 void KLed::setDarkFactor(int darkFactor)
110 {
111     if (d->darkFactor == darkFactor) {
112         return;
113     }
114 
115     d->darkFactor = darkFactor;
116     updateCachedPixmap();
117 }
118 
darkFactor() const119 int KLed::darkFactor() const
120 {
121     return d->darkFactor;
122 }
123 
setLook(Look look)124 void KLed::setLook(Look look)
125 {
126     if (d->look == look) {
127         return;
128     }
129 
130     d->look = look;
131     updateCachedPixmap();
132 }
133 
toggle()134 void KLed::toggle()
135 {
136     d->state = (d->state == On ? Off : On);
137     updateCachedPixmap();
138     updateAccessibleName();
139 }
140 
on()141 void KLed::on()
142 {
143     setState(On);
144 }
145 
off()146 void KLed::off()
147 {
148     setState(Off);
149 }
150 
resizeEvent(QResizeEvent *)151 void KLed::resizeEvent(QResizeEvent *)
152 {
153     updateCachedPixmap();
154 }
155 
sizeHint() const156 QSize KLed::sizeHint() const
157 {
158     QStyleOption option;
159     option.initFrom(this);
160     int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, &option, this);
161     return QSize(iconSize, iconSize);
162 }
163 
minimumSizeHint() const164 QSize KLed::minimumSizeHint() const
165 {
166     return QSize(16, 16);
167 }
168 
updateAccessibleName()169 void KLed::updateAccessibleName()
170 {
171 #ifndef QT_NO_ACCESSIBILITY
172     QString onName = tr("LED on", "Accessible name of a Led whose state is on");
173     QString offName = tr("LED off", "Accessible name of a Led whose state is off");
174     QString lastName = accessibleName();
175 
176     if (lastName.isEmpty() || lastName == onName || lastName == offName) {
177         // Accessible name has not been manually set.
178 
179         setAccessibleName(d->state == On ? onName : offName);
180     }
181 #endif
182 }
183 
updateCachedPixmap()184 void KLed::updateCachedPixmap()
185 {
186     d->cachedPixmap[Off] = QPixmap();
187     d->cachedPixmap[On] = QPixmap();
188     update();
189 }
190 
paintEvent(QPaintEvent *)191 void KLed::paintEvent(QPaintEvent *)
192 {
193     if (!d->cachedPixmap[d->state].isNull()) {
194         QPainter painter(this);
195         painter.drawPixmap(1, 1, d->cachedPixmap[d->state]);
196         return;
197     }
198 
199     QSize size(width() - 2, height() - 2);
200     if (d->shape == Circular) {
201         // Make sure the LED is round
202         const int dim = qMin(width(), height()) - 2;
203         size = QSize(dim, dim);
204     }
205     QPointF center(size.width() / 2.0, size.height() / 2.0);
206     const int smallestSize = qMin(size.width(), size.height());
207     QPainter painter;
208 
209     QImage image(size, QImage::Format_ARGB32_Premultiplied);
210     image.fill(0);
211 
212     QRadialGradient fillGradient(center, smallestSize / 2.0, QPointF(center.x(), size.height() / 3.0));
213     const QColor fillColor = d->state != Off ? d->color : d->color.darker(d->darkFactor);
214     fillGradient.setColorAt(0.0, fillColor.lighter(250));
215     fillGradient.setColorAt(0.5, fillColor.lighter(130));
216     fillGradient.setColorAt(1.0, fillColor);
217 
218     QConicalGradient borderGradient(center, d->look == Sunken ? 90 : -90);
219     QColor borderColor = palette().color(QPalette::Dark);
220     if (d->state == On) {
221         QColor glowOverlay = fillColor;
222         glowOverlay.setAlpha(80);
223 
224         // This isn't the fastest way, but should be "fast enough".
225         // It's also the only safe way to use QPainter::CompositionMode
226         QImage img(1, 1, QImage::Format_ARGB32_Premultiplied);
227         QPainter p(&img);
228         QColor start = borderColor;
229         start.setAlpha(255); // opaque
230         p.fillRect(0, 0, 1, 1, start);
231         p.setCompositionMode(QPainter::CompositionMode_SourceOver);
232         p.fillRect(0, 0, 1, 1, glowOverlay);
233         p.end();
234 
235         borderColor = img.pixel(0, 0);
236     }
237     borderGradient.setColorAt(0.2, borderColor);
238     borderGradient.setColorAt(0.5, palette().color(QPalette::Light));
239     borderGradient.setColorAt(0.8, borderColor);
240 
241     painter.begin(&image);
242     painter.setRenderHint(QPainter::Antialiasing);
243     painter.setBrush(d->look == Flat ? QBrush(fillColor) : QBrush(fillGradient));
244     const QBrush penBrush = (d->look == Flat) ? QBrush(borderColor) : QBrush(borderGradient);
245     const qreal penWidth = smallestSize / 8.0;
246     painter.setPen(QPen(penBrush, penWidth));
247     QRectF r(penWidth / 2.0, penWidth / 2.0, size.width() - penWidth, size.height() - penWidth);
248     if (d->shape == Rectangular) {
249         painter.drawRect(r);
250     } else {
251         painter.drawEllipse(r);
252     }
253     painter.end();
254 
255     d->cachedPixmap[d->state] = QPixmap::fromImage(image);
256     painter.begin(this);
257     painter.drawPixmap(1, 1, d->cachedPixmap[d->state]);
258     painter.end();
259 }
260