1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qprogressbar.h"
5
6#include <qlocale.h>
7#include <qevent.h>
8#include <qpainter.h>
9#include <qstylepainter.h>
10#include <qstyleoption.h>
11#include <private/qwidget_p.h>
12#if QT_CONFIG(accessibility)
13#include <qaccessible.h>
14#endif
15#include <limits.h>
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21class QProgressBarPrivate : public QWidgetPrivate
22{
23 Q_DECLARE_PUBLIC(QProgressBar)
24
25public:
26 QProgressBarPrivate();
27
28 void init();
29 void initDefaultFormat();
30 inline void resetLayoutItemMargins();
31
32 int minimum;
33 int maximum;
34 int value;
35 Qt::Alignment alignment;
36 uint textVisible : 1;
37 uint defaultFormat: 1;
38 int lastPaintedValue;
39 Qt::Orientation orientation;
40 bool invertedAppearance;
41 QProgressBar::Direction textDirection;
42 QString format;
43 inline int bound(int val) const { return qMax(a: minimum-1, b: qMin(a: maximum, b: val)); }
44 bool repaintRequired() const;
45};
46
47QProgressBarPrivate::QProgressBarPrivate()
48 : minimum(0), maximum(100), value(-1), alignment(Qt::AlignLeft), textVisible(true),
49 defaultFormat(true), lastPaintedValue(-1), orientation(Qt::Horizontal), invertedAppearance(false),
50 textDirection(QProgressBar::TopToBottom)
51{
52 initDefaultFormat();
53}
54
55void QProgressBarPrivate::initDefaultFormat()
56{
57 if (defaultFormat)
58 format = "%p"_L1 + locale.percent();
59}
60
61void QProgressBarPrivate::init()
62{
63 Q_Q(QProgressBar);
64 QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed);
65 if (orientation == Qt::Vertical)
66 sp.transpose();
67 q->setSizePolicy(sp);
68 q->setAttribute(Qt::WA_WState_OwnSizePolicy, on: false);
69 resetLayoutItemMargins();
70}
71
72void QProgressBarPrivate::resetLayoutItemMargins()
73{
74 Q_Q(QProgressBar);
75 QStyleOptionProgressBar option;
76 // ### It seems like this can be called directly from the constructor which should be avoided
77 // ### if possible, since we will not call a possible re-implemented version
78 q->initStyleOption(option: &option);
79 setLayoutItemMargins(element: QStyle::SE_ProgressBarLayoutItem, opt: &option);
80}
81
82/*!
83 Initialize \a option with the values from this QProgressBar. This method is useful
84 for subclasses when they need a QStyleOptionProgressBar,
85 but don't want to fill in all the information themselves.
86
87 \sa QStyleOption::initFrom()
88*/
89void QProgressBar::initStyleOption(QStyleOptionProgressBar *option) const
90{
91 if (!option)
92 return;
93 Q_D(const QProgressBar);
94 option->initFrom(w: this);
95
96 if (d->orientation == Qt::Horizontal)
97 option->state |= QStyle::State_Horizontal;
98 option->minimum = d->minimum;
99 option->maximum = d->maximum;
100 option->progress = d->value;
101 option->textAlignment = d->alignment;
102 option->textVisible = d->textVisible;
103 option->text = text();
104 option->invertedAppearance = d->invertedAppearance;
105 option->bottomToTop = d->textDirection == QProgressBar::BottomToTop;
106}
107
108bool QProgressBarPrivate::repaintRequired() const
109{
110 Q_Q(const QProgressBar);
111 if (value == lastPaintedValue)
112 return false;
113
114 const auto valueDifference = qAbs(t: qint64(value) - lastPaintedValue);
115 // Check if the text needs to be repainted
116 if (value == minimum || value == maximum)
117 return true;
118
119 const auto totalSteps = qint64(maximum) - minimum;
120 if (textVisible) {
121 if (format.contains(s: "%v"_L1))
122 return true;
123 if (format.contains(s: "%p"_L1) && valueDifference >= qAbs(t: totalSteps / 100))
124 return true;
125 }
126
127 // Check if the bar needs to be repainted
128 QStyleOptionProgressBar opt;
129 q->initStyleOption(option: &opt);
130 int cw = q->style()->pixelMetric(metric: QStyle::PM_ProgressBarChunkWidth, option: &opt, widget: q);
131 QRect groove = q->style()->subElementRect(subElement: QStyle::SE_ProgressBarGroove, option: &opt, widget: q);
132 // This expression is basically
133 // (valueDifference / (maximum - minimum) > cw / groove.width())
134 // transformed to avoid integer division.
135 int grooveBlock = (q->orientation() == Qt::Horizontal) ? groove.width() : groove.height();
136 return valueDifference * grooveBlock > cw * totalSteps;
137}
138
139/*!
140 \class QProgressBar
141 \brief The QProgressBar widget provides a horizontal or vertical progress bar.
142
143 \ingroup basicwidgets
144 \inmodule QtWidgets
145
146 \image windows-progressbar.png
147
148 A progress bar is used to give the user an indication of the
149 progress of an operation and to reassure them that the application
150 is still running.
151
152 The progress bar uses the concept of \e steps. You set it up by
153 specifying the minimum and maximum possible step values, and it
154 will display the percentage of steps that have been completed
155 when you later give it the current step value. The percentage is
156 calculated by dividing the progress (value() - minimum()) divided
157 by maximum() - minimum().
158
159 You can specify the minimum and maximum number of steps with
160 setMinimum() and setMaximum. The current number of steps is set
161 with setValue(). The progress bar can be rewound to the
162 beginning with reset().
163
164 If minimum and maximum both are set to 0, the bar shows a busy
165 indicator instead of a percentage of steps. This is useful, for
166 example, when using QNetworkAccessManager to download items when
167 they are unable to determine the size of the item being downloaded.
168
169 \sa QProgressDialog
170*/
171
172/*!
173 \since 4.1
174 \enum QProgressBar::Direction
175 \brief Specifies the reading direction of the \l text for vertical progress bars.
176
177 \value TopToBottom The text is rotated 90 degrees clockwise.
178 \value BottomToTop The text is rotated 90 degrees counter-clockwise.
179
180 Note that whether or not the text is drawn is dependent on the style.
181 Currently CleanLooks and Plastique draw the text. Mac, Windows
182 and WindowsVista style do not.
183
184 \sa textDirection
185*/
186
187/*!
188 \fn void QProgressBar::valueChanged(int value)
189
190 This signal is emitted when the value shown in the progress bar changes.
191 \a value is the new value shown by the progress bar.
192*/
193
194/*!
195 Constructs a progress bar with the given \a parent.
196
197 By default, the minimum step value is set to 0, and the maximum to 100.
198
199 \sa setRange()
200*/
201
202QProgressBar::QProgressBar(QWidget *parent)
203 : QWidget(*(new QProgressBarPrivate), parent, { })
204{
205 d_func()->init();
206}
207
208/*!
209 Destructor.
210*/
211QProgressBar::~QProgressBar()
212{
213}
214
215/*!
216 Reset the progress bar. The progress bar "rewinds" and shows no
217 progress.
218*/
219
220void QProgressBar::reset()
221{
222 Q_D(QProgressBar);
223 if (d->minimum == INT_MIN)
224 d->value = INT_MIN;
225 else
226 d->value = d->minimum - 1;
227 repaint();
228}
229
230/*!
231 \property QProgressBar::minimum
232 \brief the progress bar's minimum value
233
234 When setting this property, the \l maximum is adjusted if
235 necessary to ensure that the range remains valid. If the
236 current value falls outside the new range, the progress bar is reset
237 with reset().
238*/
239void QProgressBar::setMinimum(int minimum)
240{
241 setRange(minimum, maximum: qMax(a: d_func()->maximum, b: minimum));
242}
243
244int QProgressBar::minimum() const
245{
246 return d_func()->minimum;
247}
248
249
250/*!
251 \property QProgressBar::maximum
252 \brief the progress bar's maximum value
253
254 When setting this property, the \l minimum is adjusted if
255 necessary to ensure that the range remains valid. If the
256 current value falls outside the new range, the progress bar is reset
257 with reset().
258*/
259
260void QProgressBar::setMaximum(int maximum)
261{
262 setRange(minimum: qMin(a: d_func()->minimum, b: maximum), maximum);
263}
264
265int QProgressBar::maximum() const
266{
267 return d_func()->maximum;
268}
269
270/*!
271 \property QProgressBar::value
272 \brief the progress bar's current value
273
274 Attempting to change the current value to one outside
275 the minimum-maximum range has no effect on the current value.
276*/
277void QProgressBar::setValue(int value)
278{
279 Q_D(QProgressBar);
280 if (d->value == value
281 || ((value > d->maximum || value < d->minimum)
282 && (d->maximum != 0 || d->minimum != 0)))
283 return;
284 d->value = value;
285 emit valueChanged(value);
286#if QT_CONFIG(accessibility)
287 if (isVisible()) {
288 QAccessibleValueChangeEvent event(this, value);
289 QAccessible::updateAccessibility(event: &event);
290 }
291#endif
292 if (d->repaintRequired())
293 repaint();
294}
295
296int QProgressBar::value() const
297{
298 return d_func()->value;
299}
300
301/*!
302 Sets the progress bar's minimum and maximum values to \a minimum and
303 \a maximum respectively.
304
305 If \a maximum is smaller than \a minimum, \a minimum becomes the only
306 legal value.
307
308 If the current value falls outside the new range, the progress bar is reset
309 with reset().
310
311 The QProgressBar can be set to undetermined state by using setRange(0, 0).
312
313 \sa minimum, maximum
314*/
315void QProgressBar::setRange(int minimum, int maximum)
316{
317 Q_D(QProgressBar);
318 if (minimum != d->minimum || maximum != d->maximum) {
319 d->minimum = minimum;
320 d->maximum = qMax(a: minimum, b: maximum);
321
322 if (d->value < qint64(d->minimum) - 1 || d->value > d->maximum)
323 reset();
324 else
325 update();
326 }
327}
328
329/*!
330 \property QProgressBar::textVisible
331 \brief whether the current completed percentage should be displayed
332
333 This property may be ignored by the style (e.g., QMacStyle never draws the text).
334
335 \sa textDirection
336*/
337void QProgressBar::setTextVisible(bool visible)
338{
339 Q_D(QProgressBar);
340 if (d->textVisible != visible) {
341 d->textVisible = visible;
342 repaint();
343 }
344}
345
346bool QProgressBar::isTextVisible() const
347{
348 return d_func()->textVisible;
349}
350
351/*!
352 \property QProgressBar::alignment
353 \brief the alignment of the progress bar
354*/
355void QProgressBar::setAlignment(Qt::Alignment alignment)
356{
357 if (d_func()->alignment != alignment) {
358 d_func()->alignment = alignment;
359 repaint();
360 }
361}
362
363Qt::Alignment QProgressBar::alignment() const
364{
365 return d_func()->alignment;
366}
367
368/*!
369 \reimp
370*/
371void QProgressBar::paintEvent(QPaintEvent *)
372{
373 QStylePainter paint(this);
374 QStyleOptionProgressBar opt;
375 initStyleOption(option: &opt);
376 paint.drawControl(ce: QStyle::CE_ProgressBar, opt);
377 d_func()->lastPaintedValue = d_func()->value;
378}
379
380/*!
381 \reimp
382*/
383QSize QProgressBar::sizeHint() const
384{
385 ensurePolished();
386 QFontMetrics fm = fontMetrics();
387 QStyleOptionProgressBar opt;
388 initStyleOption(option: &opt);
389 int cw = style()->pixelMetric(metric: QStyle::PM_ProgressBarChunkWidth, option: &opt, widget: this);
390 QSize size = QSize(qMax(a: 9, b: cw) * 7 + fm.horizontalAdvance(u'0') * 4, fm.height() + 8);
391 if (!(opt.state & QStyle::State_Horizontal))
392 size = size.transposed();
393 return style()->sizeFromContents(ct: QStyle::CT_ProgressBar, opt: &opt, contentsSize: size, w: this);
394}
395
396/*!
397 \reimp
398*/
399QSize QProgressBar::minimumSizeHint() const
400{
401 QSize size;
402 if (orientation() == Qt::Horizontal)
403 size = QSize(sizeHint().width(), fontMetrics().height() + 2);
404 else
405 size = QSize(fontMetrics().height() + 2, sizeHint().height());
406 return size;
407}
408
409/*!
410 \property QProgressBar::text
411 \brief the descriptive text shown with the progress bar
412
413 The text returned is the same as the text displayed in the center
414 (or in some styles, to the left) of the progress bar.
415
416 The progress shown in the text may be smaller than the minimum value,
417 indicating that the progress bar is in the "reset" state before any
418 progress is set.
419
420 In the default implementation, the text either contains a percentage
421 value that indicates the progress so far, or it is blank because the
422 progress bar is in the reset state.
423*/
424QString QProgressBar::text() const
425{
426 Q_D(const QProgressBar);
427 if ((d->maximum == 0 && d->minimum == 0) || d->value < d->minimum
428 || (d->value == INT_MIN && d->minimum == INT_MIN))
429 return QString();
430
431 qint64 totalSteps = qint64(d->maximum) - d->minimum;
432
433 QString result = d->format;
434 QLocale locale = d->locale; // Omit group separators for compatibility with previous versions that were non-localized.
435 locale.setNumberOptions(locale.numberOptions() | QLocale::OmitGroupSeparator);
436 result.replace(before: "%m"_L1, after: locale.toString(i: totalSteps));
437 result.replace(before: "%v"_L1, after: locale.toString(i: d->value));
438
439 // If max and min are equal and we get this far, it means that the
440 // progress bar has one step and that we are on that step. Return
441 // 100% here in order to avoid division by zero further down.
442 if (totalSteps == 0) {
443 result.replace(before: "%p"_L1, after: locale.toString(i: 100));
444 return result;
445 }
446
447 const auto progress = static_cast<int>((qint64(d->value) - d->minimum) * 100.0 / totalSteps);
448 result.replace(before: "%p"_L1, after: locale.toString(i: progress));
449 return result;
450}
451
452/*!
453 \since 4.1
454 \property QProgressBar::orientation
455 \brief the orientation of the progress bar
456
457 The orientation must be \l Qt::Horizontal (the default) or \l
458 Qt::Vertical.
459
460 \sa invertedAppearance, textDirection
461*/
462
463void QProgressBar::setOrientation(Qt::Orientation orientation)
464{
465 Q_D(QProgressBar);
466 if (d->orientation == orientation)
467 return;
468 d->orientation = orientation;
469 if (!testAttribute(attribute: Qt::WA_WState_OwnSizePolicy)) {
470 setSizePolicy(sizePolicy().transposed());
471 setAttribute(Qt::WA_WState_OwnSizePolicy, on: false);
472 }
473 d->resetLayoutItemMargins();
474 update();
475 updateGeometry();
476}
477
478Qt::Orientation QProgressBar::orientation() const
479{
480 Q_D(const QProgressBar);
481 return d->orientation;
482}
483
484/*!
485 \since 4.1
486 \property QProgressBar::invertedAppearance
487 \brief whether or not a progress bar shows its progress inverted
488
489 If this property is \c true, the progress bar grows in the other
490 direction (e.g. from right to left). By default, the progress bar
491 is not inverted.
492
493 \sa orientation, layoutDirection
494*/
495
496void QProgressBar::setInvertedAppearance(bool invert)
497{
498 Q_D(QProgressBar);
499 d->invertedAppearance = invert;
500 update();
501}
502
503bool QProgressBar::invertedAppearance() const
504{
505 Q_D(const QProgressBar);
506 return d->invertedAppearance;
507}
508
509/*!
510 \since 4.1
511 \property QProgressBar::textDirection
512 \brief the reading direction of the \l text for vertical progress bars
513
514 This property has no impact on horizontal progress bars.
515 By default, the reading direction is QProgressBar::TopToBottom.
516
517 \sa orientation, textVisible
518*/
519void QProgressBar::setTextDirection(QProgressBar::Direction textDirection)
520{
521 Q_D(QProgressBar);
522 d->textDirection = textDirection;
523 update();
524}
525
526QProgressBar::Direction QProgressBar::textDirection() const
527{
528 Q_D(const QProgressBar);
529 return d->textDirection;
530}
531
532/*! \reimp */
533bool QProgressBar::event(QEvent *e)
534{
535 Q_D(QProgressBar);
536 switch (e->type()) {
537 case QEvent::StyleChange:
538#ifdef Q_OS_MAC
539 case QEvent::MacSizeChange:
540#endif
541 d->resetLayoutItemMargins();
542 break;
543 case QEvent::LocaleChange:
544 d->initDefaultFormat();
545 break;
546 default:
547 break;
548 }
549 return QWidget::event(event: e);
550}
551
552/*!
553 \since 4.2
554 \property QProgressBar::format
555 \brief the string used to generate the current text
556
557 %p - is replaced by the percentage completed.
558 %v - is replaced by the current value.
559 %m - is replaced by the total number of steps.
560
561 The default value is "%p%".
562
563 \sa text()
564*/
565void QProgressBar::setFormat(const QString &format)
566{
567 Q_D(QProgressBar);
568 if (d->format == format)
569 return;
570 d->format = format;
571 d->defaultFormat = false;
572 update();
573}
574
575void QProgressBar::resetFormat()
576{
577 Q_D(QProgressBar);
578 d->defaultFormat = true;
579 d->initDefaultFormat();
580 update();
581}
582
583QString QProgressBar::format() const
584{
585 Q_D(const QProgressBar);
586 return d->format;
587}
588
589QT_END_NAMESPACE
590
591#include "moc_qprogressbar.cpp"
592

source code of qtbase/src/widgets/widgets/qprogressbar.cpp