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