| 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 | |
| 28 | KLed::KLed(QWidget *parent) |
| 29 | : QWidget(parent) |
| 30 | , d(new KLedPrivate) |
| 31 | { |
| 32 | setColor(Qt::green); |
| 33 | updateAccessibleName(); |
| 34 | } |
| 35 | |
| 36 | KLed::KLed(const QColor &color, QWidget *parent) |
| 37 | : QWidget(parent) |
| 38 | , d(new KLedPrivate) |
| 39 | { |
| 40 | setColor(color); |
| 41 | updateAccessibleName(); |
| 42 | } |
| 43 | |
| 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 | |
| 58 | KLed::State KLed::state() const |
| 59 | { |
| 60 | return d->state; |
| 61 | } |
| 62 | |
| 63 | KLed::Shape KLed::shape() const |
| 64 | { |
| 65 | return d->shape; |
| 66 | } |
| 67 | |
| 68 | QColor KLed::color() const |
| 69 | { |
| 70 | return d->color; |
| 71 | } |
| 72 | |
| 73 | KLed::Look KLed::look() const |
| 74 | { |
| 75 | return d->look; |
| 76 | } |
| 77 | |
| 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 | |
| 89 | void KLed::setShape(Shape shape) |
| 90 | { |
| 91 | if (d->shape == shape) { |
| 92 | return; |
| 93 | } |
| 94 | |
| 95 | d->shape = shape; |
| 96 | updateCachedPixmap(); |
| 97 | } |
| 98 | |
| 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 | |
| 109 | void KLed::setDarkFactor(int darkFactor) |
| 110 | { |
| 111 | if (d->darkFactor == darkFactor) { |
| 112 | return; |
| 113 | } |
| 114 | |
| 115 | d->darkFactor = darkFactor; |
| 116 | updateCachedPixmap(); |
| 117 | } |
| 118 | |
| 119 | int KLed::darkFactor() const |
| 120 | { |
| 121 | return d->darkFactor; |
| 122 | } |
| 123 | |
| 124 | void KLed::setLook(Look look) |
| 125 | { |
| 126 | if (d->look == look) { |
| 127 | return; |
| 128 | } |
| 129 | |
| 130 | d->look = look; |
| 131 | updateCachedPixmap(); |
| 132 | } |
| 133 | |
| 134 | void KLed::toggle() |
| 135 | { |
| 136 | d->state = (d->state == On ? Off : On); |
| 137 | updateCachedPixmap(); |
| 138 | updateAccessibleName(); |
| 139 | } |
| 140 | |
| 141 | void KLed::on() |
| 142 | { |
| 143 | setState(On); |
| 144 | } |
| 145 | |
| 146 | void KLed::off() |
| 147 | { |
| 148 | setState(Off); |
| 149 | } |
| 150 | |
| 151 | void KLed::resizeEvent(QResizeEvent *) |
| 152 | { |
| 153 | updateCachedPixmap(); |
| 154 | } |
| 155 | |
| 156 | QSize KLed::sizeHint() const |
| 157 | { |
| 158 | QStyleOption option; |
| 159 | option.initFrom(w: this); |
| 160 | int iconSize = style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: &option, widget: this); |
| 161 | return QSize(iconSize, iconSize); |
| 162 | } |
| 163 | |
| 164 | QSize KLed::minimumSizeHint() const |
| 165 | { |
| 166 | return QSize(16, 16); |
| 167 | } |
| 168 | |
| 169 | void KLed::updateAccessibleName() |
| 170 | { |
| 171 | #ifndef QT_NO_ACCESSIBILITY |
| 172 | QString onName = tr(s: "LED on" , c: "Accessible name of a Led whose state is on" ); |
| 173 | QString offName = tr(s: "LED off" , c: "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 | |
| 184 | void KLed::updateCachedPixmap() |
| 185 | { |
| 186 | d->cachedPixmap[Off] = QPixmap(); |
| 187 | d->cachedPixmap[On] = QPixmap(); |
| 188 | update(); |
| 189 | } |
| 190 | |
| 191 | void KLed::paintEvent(QPaintEvent *) |
| 192 | { |
| 193 | if (!d->cachedPixmap[d->state].isNull()) { |
| 194 | QPainter painter(this); |
| 195 | painter.drawPixmap(x: 1, y: 1, pm: 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(a: width(), b: height()) - 2; |
| 203 | size = QSize(dim, dim); |
| 204 | } |
| 205 | QPointF center(size.width() / 2.0, size.height() / 2.0); |
| 206 | const int smallestSize = qMin(a: size.width(), b: size.height()); |
| 207 | QPainter painter; |
| 208 | |
| 209 | QImage image(size, QImage::Format_ARGB32_Premultiplied); |
| 210 | image.fill(pixel: 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(f: d->darkFactor); |
| 214 | fillGradient.setColorAt(pos: 0.0, color: fillColor.lighter(f: 250)); |
| 215 | fillGradient.setColorAt(pos: 0.5, color: fillColor.lighter(f: 130)); |
| 216 | fillGradient.setColorAt(pos: 1.0, color: fillColor); |
| 217 | |
| 218 | QConicalGradient borderGradient(center, d->look == Sunken ? 90 : -90); |
| 219 | QColor borderColor = palette().color(cr: 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(x: 0, y: 0, w: 1, h: 1, b: start); |
| 231 | p.setCompositionMode(QPainter::CompositionMode_SourceOver); |
| 232 | p.fillRect(x: 0, y: 0, w: 1, h: 1, b: glowOverlay); |
| 233 | p.end(); |
| 234 | |
| 235 | borderColor = img.pixel(x: 0, y: 0); |
| 236 | } |
| 237 | borderGradient.setColorAt(pos: 0.2, color: borderColor); |
| 238 | borderGradient.setColorAt(pos: 0.5, color: palette().color(cr: QPalette::Light)); |
| 239 | borderGradient.setColorAt(pos: 0.8, color: borderColor); |
| 240 | |
| 241 | painter.begin(&image); |
| 242 | painter.setRenderHint(hint: 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(rect: 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(x: 1, y: 1, pm: d->cachedPixmap[d->state]); |
| 258 | painter.end(); |
| 259 | } |
| 260 | |
| 261 | #include "moc_kled.cpp" |
| 262 | |