1 /*
2     QRoundProgressBar - a circular progress bar Qt widget.
3 
4     Sintegrial Technologies (c) 2015-now
5 
6     The software is freeware and is distributed "as is" with the complete source codes.
7     Anybody is free to use it in any software projects, either commercial or non-commercial.
8     Please do not remove this copyright message and remain the name of the author unchanged.
9 
10     It is very appreciated if you produce some feedback to us case you are going to use
11     the software.
12 
13     Please send your questions, suggestions, and information about found issues to the
14 
15     sintegrial@gmail.com
16 
17 */
18 
19 #include "QRoundProgressBar.h"
20 
21 #include <QPainter>
22 #include <QPainterPath>
23 
QRoundProgressBar(QWidget * parent)24 QRoundProgressBar::QRoundProgressBar(QWidget *parent)
25     : QWidget(parent), m_min(0), m_max(100), m_value(25), m_nullPosition(PositionTop), m_barStyle(StyleDonut),
26       m_outlinePenWidth(1), m_dataPenWidth(1), m_rebuildBrush(false), m_format("%p%"), m_decimals(1),
27       m_updateFlags(UF_PERCENT)
28 {
29 }
30 
setRange(double min,double max)31 void QRoundProgressBar::setRange(double min, double max)
32 {
33     m_min = min;
34     m_max = max;
35 
36     if (m_max < m_min)
37         qSwap(m_max, m_min);
38 
39     if (m_value < m_min)
40         m_value = m_min;
41     else if (m_value > m_max)
42         m_value = m_max;
43 
44     if (!m_gradientData.isEmpty())
45         m_rebuildBrush = true;
46 
47     update();
48 }
49 
setMinimum(double min)50 void QRoundProgressBar::setMinimum(double min)
51 {
52     setRange(min, m_max);
53 }
54 
setMaximum(double max)55 void QRoundProgressBar::setMaximum(double max)
56 {
57     setRange(m_min, max);
58 }
59 
setValue(double val)60 void QRoundProgressBar::setValue(double val)
61 {
62     if (m_value != val)
63     {
64         if (val < m_min)
65             m_value = m_min;
66         else if (val > m_max)
67             m_value = m_max;
68         else
69             m_value = val;
70 
71         update();
72     }
73 }
74 
setValue(int val)75 void QRoundProgressBar::setValue(int val)
76 {
77     setValue(double(val));
78 }
79 
setNullPosition(double position)80 void QRoundProgressBar::setNullPosition(double position)
81 {
82     if (position != m_nullPosition)
83     {
84         m_nullPosition = position;
85 
86         if (!m_gradientData.isEmpty())
87             m_rebuildBrush = true;
88 
89         update();
90     }
91 }
92 
setBarStyle(QRoundProgressBar::BarStyle style)93 void QRoundProgressBar::setBarStyle(QRoundProgressBar::BarStyle style)
94 {
95     if (style != m_barStyle)
96     {
97         m_barStyle = style;
98 
99         update();
100     }
101 }
102 
setOutlinePenWidth(double penWidth)103 void QRoundProgressBar::setOutlinePenWidth(double penWidth)
104 {
105     if (penWidth != m_outlinePenWidth)
106     {
107         m_outlinePenWidth = penWidth;
108 
109         update();
110     }
111 }
112 
setDataPenWidth(double penWidth)113 void QRoundProgressBar::setDataPenWidth(double penWidth)
114 {
115     if (penWidth != m_dataPenWidth)
116     {
117         m_dataPenWidth = penWidth;
118 
119         update();
120     }
121 }
122 
setDataColors(const QGradientStops & stopPoints)123 void QRoundProgressBar::setDataColors(const QGradientStops &stopPoints)
124 {
125     if (stopPoints != m_gradientData)
126     {
127         m_gradientData = stopPoints;
128         m_rebuildBrush = true;
129 
130         update();
131     }
132 }
133 
setFormat(const QString & format)134 void QRoundProgressBar::setFormat(const QString &format)
135 {
136     if (format != m_format)
137     {
138         m_format = format;
139 
140         valueFormatChanged();
141     }
142 }
143 
resetFormat()144 void QRoundProgressBar::resetFormat()
145 {
146     m_format.clear();
147     valueFormatChanged();
148 }
149 
setDecimals(int count)150 void QRoundProgressBar::setDecimals(int count)
151 {
152     if (count >= 0 && count != m_decimals)
153     {
154         m_decimals = count;
155 
156         valueFormatChanged();
157     }
158 }
159 
paintEvent(QPaintEvent *)160 void QRoundProgressBar::paintEvent(QPaintEvent * /*event*/)
161 {
162 #if 0
163     double outerRadius = qMin(width(), height());
164     QRectF baseRect(1, 1, outerRadius-2, outerRadius-2);
165 
166     QImage buffer(outerRadius, outerRadius, QImage::Format_ARGB32_Premultiplied);
167 
168     QPainter p(&buffer);
169     p.setRenderHint(QPainter::Antialiasing);
170 
171     // data brush
172     rebuildDataBrushIfNeeded();
173 
174     // background
175     drawBackground(p, buffer.rect());
176 
177     // base circle
178     drawBase(p, baseRect);
179 
180     // data circle
181     double arcStep = 360.0 / (m_max - m_min) * m_value;
182     drawValue(p, baseRect, m_value, arcStep);
183 
184     // center circle
185     double innerRadius(0);
186     QRectF innerRect;
187     calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius);
188     drawInnerBackground(p, innerRect);
189 
190     // text
191     drawText(p, innerRect, innerRadius, m_value);
192 
193     // finally draw the bar
194     p.end();
195 
196     QPainter painter(this);
197     painter.fillRect(baseRect, palette().background());
198     painter.drawImage(0,0, buffer);
199 #endif
200     double outerRadius = qMin(width(), height());
201     QRectF baseRect(1, 1, outerRadius - 2, outerRadius - 2);
202     QPainter p(this);
203     //painter.fillRect(baseRect, palette().window());
204     p.setRenderHint(QPainter::Antialiasing);
205     p.fillRect(baseRect, Qt::NoBrush);
206     drawBase(p, baseRect);
207     // data circle
208     double arcStep = 360.0 / (m_max - m_min) * m_value;
209     drawValue(p, baseRect, m_value, arcStep);
210 
211     // center circle
212     double innerRadius(0);
213     QRectF innerRect;
214     calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius);
215     drawInnerBackground(p, innerRect);
216 
217     // text
218     drawText(p, innerRect, innerRadius, m_value);
219 }
220 
drawBackground(QPainter & p,const QRectF & baseRect)221 void QRoundProgressBar::drawBackground(QPainter &p, const QRectF &baseRect)
222 {
223     p.fillRect(baseRect, palette().window());
224 }
225 
drawBase(QPainter & p,const QRectF & baseRect)226 void QRoundProgressBar::drawBase(QPainter &p, const QRectF &baseRect)
227 {
228     switch (m_barStyle)
229     {
230         case StyleDonut:
231             p.setPen(QPen(palette().shadow().color(), m_outlinePenWidth));
232             p.setBrush(palette().base());
233             p.drawEllipse(baseRect);
234             break;
235 
236         case StylePie:
237             p.setPen(QPen(palette().base().color(), m_outlinePenWidth));
238             p.setBrush(palette().base());
239             p.drawEllipse(baseRect);
240             break;
241 
242         case StyleLine:
243             p.setPen(QPen(palette().base().color(), m_outlinePenWidth));
244             p.setBrush(Qt::NoBrush);
245             p.drawEllipse(baseRect.adjusted(m_outlinePenWidth / 2, m_outlinePenWidth / 2, -m_outlinePenWidth / 2,
246                                             -m_outlinePenWidth / 2));
247             break;
248 
249         default:;
250     }
251 }
252 
drawValue(QPainter & p,const QRectF & baseRect,double value,double arcLength)253 void QRoundProgressBar::drawValue(QPainter &p, const QRectF &baseRect, double value, double arcLength)
254 {
255     // nothing to draw
256     if (value == m_min)
257         return;
258 
259     // for Line style
260     if (m_barStyle == StyleLine)
261     {
262         p.setPen(QPen(palette().highlight().color(), m_dataPenWidth));
263         p.setBrush(Qt::NoBrush);
264         p.drawArc(baseRect.adjusted(m_outlinePenWidth / 2, m_outlinePenWidth / 2, -m_outlinePenWidth / 2,
265                                     -m_outlinePenWidth / 2),
266                   m_nullPosition * 16, -arcLength * 16);
267         return;
268     }
269 
270     // for Pie and Donut styles
271     QPainterPath dataPath;
272     dataPath.setFillRule(Qt::WindingFill);
273 
274     // pie segment outer
275     dataPath.moveTo(baseRect.center());
276     dataPath.arcTo(baseRect, m_nullPosition, -arcLength);
277     dataPath.lineTo(baseRect.center());
278 
279     p.setBrush(palette().highlight());
280     p.setPen(QPen(palette().shadow().color(), m_dataPenWidth));
281     p.drawPath(dataPath);
282 }
283 
calculateInnerRect(const QRectF &,double outerRadius,QRectF & innerRect,double & innerRadius)284 void QRoundProgressBar::calculateInnerRect(const QRectF & /*baseRect*/, double outerRadius, QRectF &innerRect,
285                                            double &innerRadius)
286 {
287     // for Line style
288     if (m_barStyle == StyleLine)
289     {
290         innerRadius = outerRadius - m_outlinePenWidth;
291     }
292     else // for Pie and Donut styles
293     {
294         innerRadius = outerRadius * 0.75;
295     }
296 
297     double delta = (outerRadius - innerRadius) / 2;
298     innerRect    = QRectF(delta, delta, innerRadius, innerRadius);
299 }
300 
drawInnerBackground(QPainter & p,const QRectF & innerRect)301 void QRoundProgressBar::drawInnerBackground(QPainter &p, const QRectF &innerRect)
302 {
303     if (m_barStyle == StyleDonut)
304     {
305         p.setBrush(palette().alternateBase());
306         p.drawEllipse(innerRect);
307     }
308 }
309 
drawText(QPainter & p,const QRectF & innerRect,double innerRadius,double value)310 void QRoundProgressBar::drawText(QPainter &p, const QRectF &innerRect, double innerRadius, double value)
311 {
312     if (m_format.isEmpty())
313         return;
314 
315     // !!! to revise
316     QFont f(font());
317     f.setPixelSize(innerRadius * qMax(0.05, (0.35 - (double)m_decimals * 0.08)));
318     p.setFont(f);
319 
320     QRectF textRect(innerRect);
321     p.setPen(palette().text().color());
322     p.drawText(textRect, Qt::AlignCenter, valueToText(value));
323 }
324 
valueToText(double value) const325 QString QRoundProgressBar::valueToText(double value) const
326 {
327     QString textToDraw(m_format);
328 
329     if (m_updateFlags & UF_VALUE)
330         textToDraw.replace("%v", QString::number(value, 'f', m_decimals));
331 
332     if (m_updateFlags & UF_PERCENT)
333     {
334         double procent = (value - m_min) / (m_max - m_min) * 100.0;
335         textToDraw.replace("%p", QString::number(procent, 'f', m_decimals));
336     }
337 
338     if (m_updateFlags & UF_MAX)
339         textToDraw.replace("%m", QString::number(m_max - m_min + 1, 'f', m_decimals));
340 
341     return textToDraw;
342 }
343 
valueFormatChanged()344 void QRoundProgressBar::valueFormatChanged()
345 {
346     m_updateFlags = 0;
347 
348     if (m_format.contains("%v"))
349         m_updateFlags |= UF_VALUE;
350 
351     if (m_format.contains("%p"))
352         m_updateFlags |= UF_PERCENT;
353 
354     if (m_format.contains("%m"))
355         m_updateFlags |= UF_MAX;
356 
357     update();
358 }
359 
rebuildDataBrushIfNeeded()360 void QRoundProgressBar::rebuildDataBrushIfNeeded()
361 {
362     if (m_rebuildBrush)
363     {
364         m_rebuildBrush = false;
365 
366         QConicalGradient dataBrush;
367         dataBrush.setCenter(0.5, 0.5);
368         dataBrush.setCoordinateMode(QGradient::StretchToDeviceMode);
369 
370         // invert colors
371         for (int i = 0; i < m_gradientData.count(); i++)
372         {
373             dataBrush.setColorAt(1.0 - m_gradientData.at(i).first, m_gradientData.at(i).second);
374         }
375 
376         // angle
377         dataBrush.setAngle(m_nullPosition);
378 
379         QPalette p(palette());
380         p.setBrush(QPalette::Highlight, dataBrush);
381         setPalette(p);
382     }
383 }
384