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
23static const int LightShade = 100;
24static const int MidShade = 200;
25static const int DarkShade = 300;
26
27class KCapacityBarPrivate
28{
29public:
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
46KCapacityBar::KCapacityBar(QWidget *parent)
47 : KCapacityBar(DrawTextOutline, parent)
48{
49}
50
51KCapacityBar::KCapacityBar(KCapacityBar::DrawTextMode drawTextMode, QWidget *parent)
52 : QWidget(parent)
53 , d(new KCapacityBarPrivate(drawTextMode))
54{
55 d->ce_capacityBar = KStyleExtensions::customControlElement(QStringLiteral("CE_CapacityBar"), widget: this);
56}
57
58KCapacityBar::~KCapacityBar() = default;
59
60void KCapacityBar::setValue(int value)
61{
62 d->value = value;
63 update();
64}
65
66int KCapacityBar::value() const
67{
68 return d->value;
69}
70
71void 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
86QString KCapacityBar::text() const
87{
88 return d->text;
89}
90
91void KCapacityBar::setFillFullBlocks(bool fillFullBlocks)
92{
93 d->fillFullBlocks = fillFullBlocks;
94 update();
95}
96
97bool KCapacityBar::fillFullBlocks() const
98{
99 return d->fillFullBlocks;
100}
101
102void KCapacityBar::setContinuous(bool continuous)
103{
104 d->continuous = continuous;
105 update();
106}
107
108bool KCapacityBar::continuous() const
109{
110 return d->continuous;
111}
112
113void 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
121int KCapacityBar::barHeight() const
122{
123 return d->barHeight;
124}
125
126void 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
139Qt::Alignment KCapacityBar::horizontalTextAlignment() const
140{
141 return d->horizontalTextAlignment;
142}
143
144void KCapacityBar::setDrawTextMode(DrawTextMode mode)
145{
146 d->drawTextMode = mode;
147 update();
148}
149
150KCapacityBar::DrawTextMode KCapacityBar::drawTextMode() const
151{
152 return d->drawTextMode;
153}
154
155void KCapacityBar::drawCapacityBar(QPainter *p, const QRect &rect) const
156{
157 drawCapacityBar(p, rect, state: {});
158}
159
160void KCapacityBar::drawCapacityBar(QPainter *p, const QRect &rect, QStyle::State state) const
161{
162 if (d->ce_capacityBar) {
163 QStyleOptionProgressBar opt;
164 opt.initFrom(w: this);
165 opt.rect = rect;
166 opt.minimum = 0;
167 opt.maximum = 100;
168 opt.progress = d->value;
169 opt.state |= (state | QStyle::State_Horizontal);
170 opt.text = d->text;
171 opt.textAlignment = Qt::AlignCenter;
172 opt.textVisible = !d->text.isEmpty();
173 style()->drawControl(element: d->ce_capacityBar, opt: &opt, p, w: this);
174
175 return;
176 }
177
178 p->setRenderHints(hints: QPainter::Antialiasing | QPainter::TextAntialiasing);
179
180 p->save();
181
182 QRect drawRect(rect);
183
184 if (d->drawTextMode == DrawTextOutline) {
185 drawRect.setHeight(d->barHeight);
186 }
187
188 QPainterPath outline;
189 outline.moveTo(x: rect.left() + ROUND_MARGIN / 4 + 1, y: rect.top());
190 outline.lineTo(x: rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1, y: rect.top());
191 outline.quadTo(ctrlPtx: rect.left() + drawRect.width() + ROUND_MARGIN / 2,
192 ctrlPty: drawRect.height() / 2 + rect.top(),
193 endPtx: rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1,
194 endPty: drawRect.height() + rect.top());
195 outline.lineTo(x: rect.left() + ROUND_MARGIN / 4 + 1, y: drawRect.height() + rect.top());
196 outline.quadTo(ctrlPtx: -ROUND_MARGIN / 2 + rect.left(), ctrlPty: drawRect.height() / 2 + rect.top(), endPtx: rect.left() + ROUND_MARGIN / 4 + 1, endPty: rect.top());
197 const QColor fillColor = palette().window().color().darker(f: DarkShade);
198 p->fillPath(path: outline, brush: QColor(fillColor.red(), fillColor.green(), fillColor.blue(), 50));
199
200 QRadialGradient bottomGradient(QPointF(rect.width() / 2, drawRect.bottom() + 1), rect.width() / 2);
201 bottomGradient.setColorAt(pos: 0, color: palette().window().color().darker(f: LightShade));
202 bottomGradient.setColorAt(pos: 1, color: Qt::transparent);
203 p->fillRect(QRect(rect.left(), drawRect.bottom() + rect.top(), rect.width(), 1), bottomGradient);
204
205 p->translate(dx: rect.left() + 2, dy: rect.top() + 1);
206
207 drawRect.setWidth(drawRect.width() - 4);
208 drawRect.setHeight(drawRect.height() - 2);
209
210 QPainterPath path;
211 path.moveTo(ROUND_MARGIN / 4, y: 0);
212 path.lineTo(x: drawRect.width() - ROUND_MARGIN / 4, y: 0);
213 path.quadTo(ctrlPtx: drawRect.width() + ROUND_MARGIN / 2, ctrlPty: drawRect.height() / 2, endPtx: drawRect.width() - ROUND_MARGIN / 4, endPty: drawRect.height());
214 path.lineTo(ROUND_MARGIN / 4, y: drawRect.height());
215 path.quadTo(ctrlPtx: -ROUND_MARGIN / 2, ctrlPty: drawRect.height() / 2, ROUND_MARGIN / 4, endPty: 0);
216
217 QLinearGradient linearGradient(0, 0, 0, drawRect.height());
218 linearGradient.setColorAt(pos: 0.5, color: palette().window().color().darker(f: MidShade));
219 linearGradient.setColorAt(pos: 1, color: palette().window().color().darker(f: LightShade));
220 p->fillPath(path, brush: linearGradient);
221
222 p->setBrush(Qt::NoBrush);
223 p->setPen(Qt::NoPen);
224
225 if (d->continuous || !d->fillFullBlocks) {
226 int start = (layoutDirection() == Qt::LeftToRight) ? -1 : (drawRect.width() + 2) - (drawRect.width() + 2) * (d->value / 100.0);
227
228 p->setClipRect(QRect(start, 0, (drawRect.width() + 2) * (d->value / 100.0), drawRect.height()), op: Qt::IntersectClip);
229 }
230
231 int left = (layoutDirection() == Qt::LeftToRight) ? 0 : drawRect.width();
232
233 int right = (layoutDirection() == Qt::LeftToRight) ? drawRect.width() : 0;
234
235 int roundMargin = (layoutDirection() == Qt::LeftToRight) ? ROUND_MARGIN : -ROUND_MARGIN;
236
237 int spacing = 2;
238 int verticalSpacing = VERTICAL_SPACING;
239 int slotWidth = 6;
240 int start = roundMargin / 4;
241
242 QPainterPath internalBar;
243 internalBar.moveTo(x: left + roundMargin / 4, y: 0);
244 internalBar.lineTo(x: right - roundMargin / 4, y: 0);
245 internalBar.quadTo(ctrlPtx: right + roundMargin / 2, ctrlPty: drawRect.height() / 2, endPtx: right - roundMargin / 4, endPty: drawRect.height());
246 internalBar.lineTo(x: left + roundMargin / 4, y: drawRect.height());
247 internalBar.quadTo(ctrlPtx: left - roundMargin / 2, ctrlPty: drawRect.height() / 2, endPtx: left + roundMargin / 4, endPty: 0);
248
249 QLinearGradient fillInternalBar(left, 0, right, 0);
250 fillInternalBar.setColorAt(pos: 0, color: palette().window().color().darker(f: MidShade));
251 fillInternalBar.setColorAt(pos: 0.5, color: palette().window().color().darker(f: LightShade));
252 fillInternalBar.setColorAt(pos: 1, color: palette().window().color().darker(f: MidShade));
253
254 if (d->drawTextMode == KCapacityBar::DrawTextInline) {
255 p->save();
256 p->setOpacity(p->opacity() * 0.7);
257 }
258
259 if (!d->continuous) {
260 int numSlots = (drawRect.width() - ROUND_MARGIN - ((slotWidth + spacing) * 2)) / (slotWidth + spacing);
261 int stopSlot = floor(x: (numSlots + 2) * (d->value / 100.0));
262
263 int plusOffset = d->fillFullBlocks ? ((drawRect.width() - ROUND_MARGIN - ((slotWidth + spacing) * 2)) - (numSlots * (slotWidth + spacing))) / 2.0 : 0;
264
265 if (!d->fillFullBlocks || stopSlot) {
266 QPainterPath firstSlot;
267 firstSlot.moveTo(x: left + roundMargin / 4, y: verticalSpacing);
268 firstSlot.lineTo(x: left + slotWidth + roundMargin / 4 + plusOffset, y: verticalSpacing);
269 firstSlot.lineTo(x: left + slotWidth + roundMargin / 4 + plusOffset, y: drawRect.height() - verticalSpacing);
270 firstSlot.lineTo(x: left + roundMargin / 4, y: drawRect.height() - verticalSpacing);
271 firstSlot.quadTo(ctrlPtx: left, ctrlPty: drawRect.height() / 2, endPtx: left + roundMargin / 4, endPty: verticalSpacing);
272 p->fillPath(path: firstSlot, brush: fillInternalBar);
273 start += slotWidth + spacing + plusOffset;
274
275 bool stopped = false;
276 for (int i = 0; i < numSlots + 1; i++) {
277 if (d->fillFullBlocks && (i == (stopSlot + 1))) {
278 stopped = true;
279 break;
280 }
281 p->fillRect(QRect(rect.left() + start, rect.top() + verticalSpacing, slotWidth, drawRect.height() - verticalSpacing * 2), fillInternalBar);
282 start += slotWidth + spacing;
283 }
284
285 if (!d->fillFullBlocks || (!stopped && (stopSlot != (numSlots + 1)) && (stopSlot != numSlots))) {
286 QPainterPath lastSlot;
287 lastSlot.moveTo(x: start, y: verticalSpacing);
288 lastSlot.lineTo(x: start, y: drawRect.height() - verticalSpacing);
289 lastSlot.lineTo(x: start + slotWidth + plusOffset, y: drawRect.height() - verticalSpacing);
290 lastSlot.quadTo(ctrlPtx: start + roundMargin, ctrlPty: drawRect.height() / 2, endPtx: start + slotWidth + plusOffset, endPty: verticalSpacing);
291 lastSlot.lineTo(x: start, y: verticalSpacing);
292 p->fillPath(path: lastSlot, brush: fillInternalBar);
293 }
294 }
295 } else {
296 p->fillPath(path: internalBar, brush: fillInternalBar);
297 }
298
299 if (d->drawTextMode == KCapacityBar::DrawTextInline) {
300 p->restore();
301 }
302
303 p->save();
304 p->setClipping(false);
305 QRadialGradient topGradient(QPointF(rect.width() / 2, drawRect.top()), rect.width() / 2);
306 const QColor fillTopColor = palette().window().color().darker(f: LightShade);
307 topGradient.setColorAt(pos: 0, color: QColor(fillTopColor.red(), fillTopColor.green(), fillTopColor.blue(), 127));
308 topGradient.setColorAt(pos: 1, color: Qt::transparent);
309 p->fillRect(QRect(rect.left(), rect.top() + drawRect.top(), rect.width(), 2), topGradient);
310 p->restore();
311
312 p->save();
313 p->setClipRect(QRect(-1, 0, rect.width(), drawRect.height() / 2), op: Qt::ReplaceClip);
314 QLinearGradient glassGradient(0, -5, 0, drawRect.height());
315 const QColor fillGlassColor = palette().base().color();
316 glassGradient.setColorAt(pos: 0, color: QColor(fillGlassColor.red(), fillGlassColor.green(), fillGlassColor.blue(), 255));
317 glassGradient.setColorAt(pos: 1, color: Qt::transparent);
318 p->fillPath(path: internalBar, brush: glassGradient);
319 p->restore();
320
321 p->restore();
322
323 if (d->drawTextMode == KCapacityBar::DrawTextInline) {
324 QRect rect(drawRect);
325 rect.setHeight(rect.height() + 4);
326 p->drawText(r: rect, flags: Qt::AlignCenter, text: fontMetrics().elidedText(text: d->text, mode: Qt::ElideRight, width: drawRect.width() - 2 * ROUND_MARGIN));
327 } else {
328 p->drawText(r: rect, flags: Qt::AlignBottom | d->horizontalTextAlignment, text: fontMetrics().elidedText(text: d->text, mode: Qt::ElideRight, width: drawRect.width()));
329 }
330}
331
332QSize KCapacityBar::minimumSizeHint() const
333{
334 int width = fontMetrics().boundingRect(text: d->text).width() + ((d->drawTextMode == KCapacityBar::DrawTextInline) ? ROUND_MARGIN * 2 : 0);
335
336 int height = (d->drawTextMode == KCapacityBar::DrawTextInline) ? qMax(a: fontMetrics().height(), b: d->barHeight)
337 : (d->text.isEmpty() ? 0 : fontMetrics().height() + VERTICAL_SPACING * 2) + d->barHeight;
338
339 if (height % 2) {
340 height++;
341 }
342
343 return QSize(width, height);
344}
345
346void KCapacityBar::paintEvent(QPaintEvent *event)
347{
348 QPainter p(this);
349 p.setClipRect(event->rect());
350 drawCapacityBar(p: &p, rect: contentsRect());
351 p.end();
352}
353
354void KCapacityBar::changeEvent(QEvent *event)
355{
356 QWidget::changeEvent(event);
357 if (event->type() == QEvent::StyleChange) {
358 d->ce_capacityBar = KStyleExtensions::customControlElement(QStringLiteral("CE_CapacityBar"), widget: this);
359 }
360}
361
362#include "moc_kcapacitybar.cpp"
363

source code of kwidgetsaddons/src/kcapacitybar.cpp