1 /*
2     This file is part of the KDE project
3     SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kcapacitybar.h"
9 #include "kstyleextensions.h"
10 
11 #include <math.h>
12 
13 #include <QLinearGradient>
14 #include <QPaintEvent>
15 #include <QPainter>
16 #include <QPainterPath>
17 #include <QStyle>
18 #include <QStyleOptionProgressBar>
19 
20 #define ROUND_MARGIN 6
21 #define VERTICAL_SPACING 1
22 
23 static const int LightShade = 100;
24 static const int MidShade = 200;
25 static const int DarkShade = 300;
26 
27 class KCapacityBarPrivate
28 {
29 public:
KCapacityBarPrivate(KCapacityBar::DrawTextMode drawTextMode)30     KCapacityBarPrivate(KCapacityBar::DrawTextMode drawTextMode)
31         : drawTextMode(drawTextMode)
32     {
33     }
34 
35     QString text;
36     int value = 0;
37     bool fillFullBlocks = true;
38     bool continuous = true;
39     int barHeight = 12;
40     Qt::Alignment horizontalTextAlignment = Qt::AlignCenter;
41     QStyle::ControlElement ce_capacityBar = QStyle::ControlElement(0);
42 
43     KCapacityBar::DrawTextMode drawTextMode;
44 };
45 
KCapacityBar(QWidget * parent)46 KCapacityBar::KCapacityBar(QWidget *parent)
47     : KCapacityBar(DrawTextOutline, parent)
48 {
49 }
50 
KCapacityBar(KCapacityBar::DrawTextMode drawTextMode,QWidget * parent)51 KCapacityBar::KCapacityBar(KCapacityBar::DrawTextMode drawTextMode, QWidget *parent)
52     : QWidget(parent)
53     , d(new KCapacityBarPrivate(drawTextMode))
54 {
55     d->ce_capacityBar = KStyleExtensions::customControlElement(QStringLiteral("CE_CapacityBar"), this);
56 }
57 
58 KCapacityBar::~KCapacityBar() = default;
59 
setValue(int value)60 void KCapacityBar::setValue(int value)
61 {
62     d->value = value;
63     update();
64 }
65 
value() const66 int KCapacityBar::value() const
67 {
68     return d->value;
69 }
70 
setText(const QString & text)71 void KCapacityBar::setText(const QString &text)
72 {
73     bool updateGeom = d->text.isEmpty() || text.isEmpty();
74     d->text = text;
75     if (updateGeom) {
76         updateGeometry();
77     }
78 
79 #ifndef QT_NO_ACCESSIBILITY
80     setAccessibleName(text);
81 #endif
82 
83     update();
84 }
85 
text() const86 QString KCapacityBar::text() const
87 {
88     return d->text;
89 }
90 
setFillFullBlocks(bool fillFullBlocks)91 void KCapacityBar::setFillFullBlocks(bool fillFullBlocks)
92 {
93     d->fillFullBlocks = fillFullBlocks;
94     update();
95 }
96 
fillFullBlocks() const97 bool KCapacityBar::fillFullBlocks() const
98 {
99     return d->fillFullBlocks;
100 }
101 
setContinuous(bool continuous)102 void KCapacityBar::setContinuous(bool continuous)
103 {
104     d->continuous = continuous;
105     update();
106 }
107 
continuous() const108 bool KCapacityBar::continuous() const
109 {
110     return d->continuous;
111 }
112 
setBarHeight(int barHeight)113 void KCapacityBar::setBarHeight(int barHeight)
114 {
115     // automatically convert odd values to even. This will make the bar look
116     // better.
117     d->barHeight = (barHeight % 2) ? barHeight + 1 : barHeight;
118     updateGeometry();
119 }
120 
barHeight() const121 int KCapacityBar::barHeight() const
122 {
123     return d->barHeight;
124 }
125 
setHorizontalTextAlignment(Qt::Alignment horizontalTextAlignment)126 void KCapacityBar::setHorizontalTextAlignment(Qt::Alignment horizontalTextAlignment)
127 {
128     Qt::Alignment alignment = horizontalTextAlignment;
129 
130     // if the value came with any vertical alignment flag, remove it.
131     alignment &= ~Qt::AlignTop;
132     alignment &= ~Qt::AlignBottom;
133     alignment &= ~Qt::AlignVCenter;
134 
135     d->horizontalTextAlignment = alignment;
136     update();
137 }
138 
horizontalTextAlignment() const139 Qt::Alignment KCapacityBar::horizontalTextAlignment() const
140 {
141     return d->horizontalTextAlignment;
142 }
143 
setDrawTextMode(DrawTextMode mode)144 void KCapacityBar::setDrawTextMode(DrawTextMode mode)
145 {
146     d->drawTextMode = mode;
147     update();
148 }
149 
drawTextMode() const150 KCapacityBar::DrawTextMode KCapacityBar::drawTextMode() const
151 {
152     return d->drawTextMode;
153 }
154 
drawCapacityBar(QPainter * p,const QRect & rect) const155 void KCapacityBar::drawCapacityBar(QPainter *p, const QRect &rect) const
156 {
157     if (d->ce_capacityBar) {
158         QStyleOptionProgressBar opt;
159         opt.initFrom(this);
160         opt.rect = rect;
161         opt.minimum = 0;
162         opt.maximum = 100;
163         opt.progress = d->value;
164         opt.state |= QStyle::State_Horizontal;
165         opt.text = d->text;
166         opt.textAlignment = Qt::AlignCenter;
167         opt.textVisible = true;
168         style()->drawControl(d->ce_capacityBar, &opt, p, this);
169 
170         return;
171     }
172 
173     p->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
174 
175     p->save();
176 
177     QRect drawRect(rect);
178 
179     if (d->drawTextMode == DrawTextOutline) {
180         drawRect.setHeight(d->barHeight);
181     }
182 
183     QPainterPath outline;
184     outline.moveTo(rect.left() + ROUND_MARGIN / 4 + 1, rect.top());
185     outline.lineTo(rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1, rect.top());
186     outline.quadTo(rect.left() + drawRect.width() + ROUND_MARGIN / 2,
187                    drawRect.height() / 2 + rect.top(),
188                    rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1,
189                    drawRect.height() + rect.top());
190     outline.lineTo(rect.left() + ROUND_MARGIN / 4 + 1, drawRect.height() + rect.top());
191     outline.quadTo(-ROUND_MARGIN / 2 + rect.left(), drawRect.height() / 2 + rect.top(), rect.left() + ROUND_MARGIN / 4 + 1, rect.top());
192     const QColor fillColor = palette().window().color().darker(DarkShade);
193     p->fillPath(outline, QColor(fillColor.red(), fillColor.green(), fillColor.blue(), 50));
194 
195     QRadialGradient bottomGradient(QPointF(rect.width() / 2, drawRect.bottom() + 1), rect.width() / 2);
196     bottomGradient.setColorAt(0, palette().window().color().darker(LightShade));
197     bottomGradient.setColorAt(1, Qt::transparent);
198     p->fillRect(QRect(rect.left(), drawRect.bottom() + rect.top(), rect.width(), 1), bottomGradient);
199 
200     p->translate(rect.left() + 2, rect.top() + 1);
201 
202     drawRect.setWidth(drawRect.width() - 4);
203     drawRect.setHeight(drawRect.height() - 2);
204 
205     QPainterPath path;
206     path.moveTo(ROUND_MARGIN / 4, 0);
207     path.lineTo(drawRect.width() - ROUND_MARGIN / 4, 0);
208     path.quadTo(drawRect.width() + ROUND_MARGIN / 2, drawRect.height() / 2, drawRect.width() - ROUND_MARGIN / 4, drawRect.height());
209     path.lineTo(ROUND_MARGIN / 4, drawRect.height());
210     path.quadTo(-ROUND_MARGIN / 2, drawRect.height() / 2, ROUND_MARGIN / 4, 0);
211 
212     QLinearGradient linearGradient(0, 0, 0, drawRect.height());
213     linearGradient.setColorAt(0.5, palette().window().color().darker(MidShade));
214     linearGradient.setColorAt(1, palette().window().color().darker(LightShade));
215     p->fillPath(path, linearGradient);
216 
217     p->setBrush(Qt::NoBrush);
218     p->setPen(Qt::NoPen);
219 
220     if (d->continuous || !d->fillFullBlocks) {
221         int start = (layoutDirection() == Qt::LeftToRight) ? -1 : (drawRect.width() + 2) - (drawRect.width() + 2) * (d->value / 100.0);
222 
223         p->setClipRect(QRect(start, 0, (drawRect.width() + 2) * (d->value / 100.0), drawRect.height()), Qt::IntersectClip);
224     }
225 
226     int left = (layoutDirection() == Qt::LeftToRight) ? 0 : drawRect.width();
227 
228     int right = (layoutDirection() == Qt::LeftToRight) ? drawRect.width() : 0;
229 
230     int roundMargin = (layoutDirection() == Qt::LeftToRight) ? ROUND_MARGIN : -ROUND_MARGIN;
231 
232     int spacing = 2;
233     int verticalSpacing = VERTICAL_SPACING;
234     int slotWidth = 6;
235     int start = roundMargin / 4;
236 
237     QPainterPath internalBar;
238     internalBar.moveTo(left + roundMargin / 4, 0);
239     internalBar.lineTo(right - roundMargin / 4, 0);
240     internalBar.quadTo(right + roundMargin / 2, drawRect.height() / 2, right - roundMargin / 4, drawRect.height());
241     internalBar.lineTo(left + roundMargin / 4, drawRect.height());
242     internalBar.quadTo(left - roundMargin / 2, drawRect.height() / 2, left + roundMargin / 4, 0);
243 
244     QLinearGradient fillInternalBar(left, 0, right, 0);
245     fillInternalBar.setColorAt(0, palette().window().color().darker(MidShade));
246     fillInternalBar.setColorAt(0.5, palette().window().color().darker(LightShade));
247     fillInternalBar.setColorAt(1, palette().window().color().darker(MidShade));
248 
249     if (d->drawTextMode == KCapacityBar::DrawTextInline) {
250         p->save();
251         p->setOpacity(p->opacity() * 0.7);
252     }
253 
254     if (!d->continuous) {
255         int numSlots = (drawRect.width() - ROUND_MARGIN - ((slotWidth + spacing) * 2)) / (slotWidth + spacing);
256         int stopSlot = floor((numSlots + 2) * (d->value / 100.0));
257 
258         int plusOffset = d->fillFullBlocks ? ((drawRect.width() - ROUND_MARGIN - ((slotWidth + spacing) * 2)) - (numSlots * (slotWidth + spacing))) / 2.0 : 0;
259 
260         if (!d->fillFullBlocks || stopSlot) {
261             QPainterPath firstSlot;
262             firstSlot.moveTo(left + roundMargin / 4, verticalSpacing);
263             firstSlot.lineTo(left + slotWidth + roundMargin / 4 + plusOffset, verticalSpacing);
264             firstSlot.lineTo(left + slotWidth + roundMargin / 4 + plusOffset, drawRect.height() - verticalSpacing);
265             firstSlot.lineTo(left + roundMargin / 4, drawRect.height() - verticalSpacing);
266             firstSlot.quadTo(left, drawRect.height() / 2, left + roundMargin / 4, verticalSpacing);
267             p->fillPath(firstSlot, fillInternalBar);
268             start += slotWidth + spacing + plusOffset;
269 
270             bool stopped = false;
271             for (int i = 0; i < numSlots + 1; i++) {
272                 if (d->fillFullBlocks && (i == (stopSlot + 1))) {
273                     stopped = true;
274                     break;
275                 }
276                 p->fillRect(QRect(rect.left() + start, rect.top() + verticalSpacing, slotWidth, drawRect.height() - verticalSpacing * 2), fillInternalBar);
277                 start += slotWidth + spacing;
278             }
279 
280             if (!d->fillFullBlocks || (!stopped && (stopSlot != (numSlots + 1)) && (stopSlot != numSlots))) {
281                 QPainterPath lastSlot;
282                 lastSlot.moveTo(start, verticalSpacing);
283                 lastSlot.lineTo(start, drawRect.height() - verticalSpacing);
284                 lastSlot.lineTo(start + slotWidth + plusOffset, drawRect.height() - verticalSpacing);
285                 lastSlot.quadTo(start + roundMargin, drawRect.height() / 2, start + slotWidth + plusOffset, verticalSpacing);
286                 lastSlot.lineTo(start, verticalSpacing);
287                 p->fillPath(lastSlot, fillInternalBar);
288             }
289         }
290     } else {
291         p->fillPath(internalBar, fillInternalBar);
292     }
293 
294     if (d->drawTextMode == KCapacityBar::DrawTextInline) {
295         p->restore();
296     }
297 
298     p->save();
299     p->setClipping(false);
300     QRadialGradient topGradient(QPointF(rect.width() / 2, drawRect.top()), rect.width() / 2);
301     const QColor fillTopColor = palette().window().color().darker(LightShade);
302     topGradient.setColorAt(0, QColor(fillTopColor.red(), fillTopColor.green(), fillTopColor.blue(), 127));
303     topGradient.setColorAt(1, Qt::transparent);
304     p->fillRect(QRect(rect.left(), rect.top() + drawRect.top(), rect.width(), 2), topGradient);
305     p->restore();
306 
307     p->save();
308     p->setClipRect(QRect(-1, 0, rect.width(), drawRect.height() / 2), Qt::ReplaceClip);
309     QLinearGradient glassGradient(0, -5, 0, drawRect.height());
310     const QColor fillGlassColor = palette().base().color();
311     glassGradient.setColorAt(0, QColor(fillGlassColor.red(), fillGlassColor.green(), fillGlassColor.blue(), 255));
312     glassGradient.setColorAt(1, Qt::transparent);
313     p->fillPath(internalBar, glassGradient);
314     p->restore();
315 
316     p->restore();
317 
318     if (d->drawTextMode == KCapacityBar::DrawTextInline) {
319         QRect rect(drawRect);
320         rect.setHeight(rect.height() + 4);
321         p->drawText(rect, Qt::AlignCenter, fontMetrics().elidedText(d->text, Qt::ElideRight, drawRect.width() - 2 * ROUND_MARGIN));
322     } else {
323         p->drawText(rect, Qt::AlignBottom | d->horizontalTextAlignment, fontMetrics().elidedText(d->text, Qt::ElideRight, drawRect.width()));
324     }
325 }
326 
minimumSizeHint() const327 QSize KCapacityBar::minimumSizeHint() const
328 {
329     int width = fontMetrics().boundingRect(d->text).width() + ((d->drawTextMode == KCapacityBar::DrawTextInline) ? ROUND_MARGIN * 2 : 0);
330 
331     int height = (d->drawTextMode == KCapacityBar::DrawTextInline) ? qMax(fontMetrics().height(), d->barHeight)
332                                                                    : (d->text.isEmpty() ? 0 : fontMetrics().height() + VERTICAL_SPACING * 2) + d->barHeight;
333 
334     if (height % 2) {
335         height++;
336     }
337 
338     return QSize(width, height);
339 }
340 
paintEvent(QPaintEvent * event)341 void KCapacityBar::paintEvent(QPaintEvent *event)
342 {
343     QPainter p(this);
344     p.setClipRect(event->rect());
345     drawCapacityBar(&p, contentsRect());
346     p.end();
347 }
348 
changeEvent(QEvent * event)349 void KCapacityBar::changeEvent(QEvent *event)
350 {
351     QWidget::changeEvent(event);
352     if (event->type() == QEvent::StyleChange) {
353         d->ce_capacityBar = KStyleExtensions::customControlElement(QStringLiteral("CE_CapacityBar"), this);
354     }
355 }
356