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: |
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 | |
46 | KCapacityBar::KCapacityBar(QWidget *parent) |
47 | : KCapacityBar(DrawTextOutline, parent) |
48 | { |
49 | } |
50 | |
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" ), widget: this); |
56 | } |
57 | |
58 | KCapacityBar::~KCapacityBar() = default; |
59 | |
60 | void KCapacityBar::setValue(int value) |
61 | { |
62 | d->value = value; |
63 | update(); |
64 | } |
65 | |
66 | int KCapacityBar::value() const |
67 | { |
68 | return d->value; |
69 | } |
70 | |
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 | |
86 | QString KCapacityBar::text() const |
87 | { |
88 | return d->text; |
89 | } |
90 | |
91 | void KCapacityBar::setFillFullBlocks(bool fillFullBlocks) |
92 | { |
93 | d->fillFullBlocks = fillFullBlocks; |
94 | update(); |
95 | } |
96 | |
97 | bool KCapacityBar::fillFullBlocks() const |
98 | { |
99 | return d->fillFullBlocks; |
100 | } |
101 | |
102 | void KCapacityBar::setContinuous(bool continuous) |
103 | { |
104 | d->continuous = continuous; |
105 | update(); |
106 | } |
107 | |
108 | bool KCapacityBar::continuous() const |
109 | { |
110 | return d->continuous; |
111 | } |
112 | |
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 | |
121 | int KCapacityBar::barHeight() const |
122 | { |
123 | return d->barHeight; |
124 | } |
125 | |
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 | |
139 | Qt::Alignment KCapacityBar::horizontalTextAlignment() const |
140 | { |
141 | return d->horizontalTextAlignment; |
142 | } |
143 | |
144 | void KCapacityBar::setDrawTextMode(DrawTextMode mode) |
145 | { |
146 | d->drawTextMode = mode; |
147 | update(); |
148 | } |
149 | |
150 | KCapacityBar::DrawTextMode KCapacityBar::drawTextMode() const |
151 | { |
152 | return d->drawTextMode; |
153 | } |
154 | |
155 | void KCapacityBar::drawCapacityBar(QPainter *p, const QRect &rect) const |
156 | { |
157 | if (d->ce_capacityBar) { |
158 | QStyleOptionProgressBar opt; |
159 | opt.initFrom(w: 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 = !d->text.isEmpty(); |
168 | style()->drawControl(element: d->ce_capacityBar, opt: &opt, p, w: this); |
169 | |
170 | return; |
171 | } |
172 | |
173 | p->setRenderHints(hints: 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(x: rect.left() + ROUND_MARGIN / 4 + 1, y: rect.top()); |
185 | outline.lineTo(x: rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1, y: rect.top()); |
186 | outline.quadTo(ctrlPtx: rect.left() + drawRect.width() + ROUND_MARGIN / 2, |
187 | ctrlPty: drawRect.height() / 2 + rect.top(), |
188 | endPtx: rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1, |
189 | endPty: drawRect.height() + rect.top()); |
190 | outline.lineTo(x: rect.left() + ROUND_MARGIN / 4 + 1, y: drawRect.height() + rect.top()); |
191 | outline.quadTo(ctrlPtx: -ROUND_MARGIN / 2 + rect.left(), ctrlPty: drawRect.height() / 2 + rect.top(), endPtx: rect.left() + ROUND_MARGIN / 4 + 1, endPty: rect.top()); |
192 | const QColor fillColor = palette().window().color().darker(f: DarkShade); |
193 | p->fillPath(path: outline, brush: 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(pos: 0, color: palette().window().color().darker(f: LightShade)); |
197 | bottomGradient.setColorAt(pos: 1, color: Qt::transparent); |
198 | p->fillRect(QRect(rect.left(), drawRect.bottom() + rect.top(), rect.width(), 1), bottomGradient); |
199 | |
200 | p->translate(dx: rect.left() + 2, dy: 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, y: 0); |
207 | path.lineTo(x: drawRect.width() - ROUND_MARGIN / 4, y: 0); |
208 | path.quadTo(ctrlPtx: drawRect.width() + ROUND_MARGIN / 2, ctrlPty: drawRect.height() / 2, endPtx: drawRect.width() - ROUND_MARGIN / 4, endPty: drawRect.height()); |
209 | path.lineTo(ROUND_MARGIN / 4, y: drawRect.height()); |
210 | path.quadTo(ctrlPtx: -ROUND_MARGIN / 2, ctrlPty: drawRect.height() / 2, ROUND_MARGIN / 4, endPty: 0); |
211 | |
212 | QLinearGradient linearGradient(0, 0, 0, drawRect.height()); |
213 | linearGradient.setColorAt(pos: 0.5, color: palette().window().color().darker(f: MidShade)); |
214 | linearGradient.setColorAt(pos: 1, color: palette().window().color().darker(f: LightShade)); |
215 | p->fillPath(path, brush: 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()), op: 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(x: left + roundMargin / 4, y: 0); |
239 | internalBar.lineTo(x: right - roundMargin / 4, y: 0); |
240 | internalBar.quadTo(ctrlPtx: right + roundMargin / 2, ctrlPty: drawRect.height() / 2, endPtx: right - roundMargin / 4, endPty: drawRect.height()); |
241 | internalBar.lineTo(x: left + roundMargin / 4, y: drawRect.height()); |
242 | internalBar.quadTo(ctrlPtx: left - roundMargin / 2, ctrlPty: drawRect.height() / 2, endPtx: left + roundMargin / 4, endPty: 0); |
243 | |
244 | QLinearGradient fillInternalBar(left, 0, right, 0); |
245 | fillInternalBar.setColorAt(pos: 0, color: palette().window().color().darker(f: MidShade)); |
246 | fillInternalBar.setColorAt(pos: 0.5, color: palette().window().color().darker(f: LightShade)); |
247 | fillInternalBar.setColorAt(pos: 1, color: palette().window().color().darker(f: 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(x: (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(x: left + roundMargin / 4, y: verticalSpacing); |
263 | firstSlot.lineTo(x: left + slotWidth + roundMargin / 4 + plusOffset, y: verticalSpacing); |
264 | firstSlot.lineTo(x: left + slotWidth + roundMargin / 4 + plusOffset, y: drawRect.height() - verticalSpacing); |
265 | firstSlot.lineTo(x: left + roundMargin / 4, y: drawRect.height() - verticalSpacing); |
266 | firstSlot.quadTo(ctrlPtx: left, ctrlPty: drawRect.height() / 2, endPtx: left + roundMargin / 4, endPty: verticalSpacing); |
267 | p->fillPath(path: firstSlot, brush: 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(x: start, y: verticalSpacing); |
283 | lastSlot.lineTo(x: start, y: drawRect.height() - verticalSpacing); |
284 | lastSlot.lineTo(x: start + slotWidth + plusOffset, y: drawRect.height() - verticalSpacing); |
285 | lastSlot.quadTo(ctrlPtx: start + roundMargin, ctrlPty: drawRect.height() / 2, endPtx: start + slotWidth + plusOffset, endPty: verticalSpacing); |
286 | lastSlot.lineTo(x: start, y: verticalSpacing); |
287 | p->fillPath(path: lastSlot, brush: fillInternalBar); |
288 | } |
289 | } |
290 | } else { |
291 | p->fillPath(path: internalBar, brush: 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(f: LightShade); |
302 | topGradient.setColorAt(pos: 0, color: QColor(fillTopColor.red(), fillTopColor.green(), fillTopColor.blue(), 127)); |
303 | topGradient.setColorAt(pos: 1, color: 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), op: Qt::ReplaceClip); |
309 | QLinearGradient glassGradient(0, -5, 0, drawRect.height()); |
310 | const QColor fillGlassColor = palette().base().color(); |
311 | glassGradient.setColorAt(pos: 0, color: QColor(fillGlassColor.red(), fillGlassColor.green(), fillGlassColor.blue(), 255)); |
312 | glassGradient.setColorAt(pos: 1, color: Qt::transparent); |
313 | p->fillPath(path: internalBar, brush: 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(r: rect, flags: Qt::AlignCenter, text: fontMetrics().elidedText(text: d->text, mode: Qt::ElideRight, width: drawRect.width() - 2 * ROUND_MARGIN)); |
322 | } else { |
323 | p->drawText(r: rect, flags: Qt::AlignBottom | d->horizontalTextAlignment, text: fontMetrics().elidedText(text: d->text, mode: Qt::ElideRight, width: drawRect.width())); |
324 | } |
325 | } |
326 | |
327 | QSize KCapacityBar::minimumSizeHint() const |
328 | { |
329 | int width = fontMetrics().boundingRect(text: d->text).width() + ((d->drawTextMode == KCapacityBar::DrawTextInline) ? ROUND_MARGIN * 2 : 0); |
330 | |
331 | int height = (d->drawTextMode == KCapacityBar::DrawTextInline) ? qMax(a: fontMetrics().height(), b: 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 | |
341 | void KCapacityBar::paintEvent(QPaintEvent *event) |
342 | { |
343 | QPainter p(this); |
344 | p.setClipRect(event->rect()); |
345 | drawCapacityBar(p: &p, rect: contentsRect()); |
346 | p.end(); |
347 | } |
348 | |
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" ), widget: this); |
354 | } |
355 | } |
356 | |
357 | #include "moc_kcapacitybar.cpp" |
358 | |