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