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 "qdial.h"
41 
42 #include <qapplication.h>
43 #include <qbitmap.h>
44 #include <qcolor.h>
45 #include <qevent.h>
46 #include <qpainter.h>
47 #include <qpolygon.h>
48 #include <qregion.h>
49 #include <qstyle.h>
50 #include <qstylepainter.h>
51 #include <qstyleoption.h>
52 #include <qslider.h>
53 #include <private/qabstractslider_p.h>
54 #include <private/qmath_p.h>
55 #ifndef QT_NO_ACCESSIBILITY
56 #include "qaccessible.h"
57 #endif
58 #include <qmath.h>
59 
60 QT_BEGIN_NAMESPACE
61 
62 class QDialPrivate : public QAbstractSliderPrivate
63 {
64     Q_DECLARE_PUBLIC(QDial)
65 public:
QDialPrivate()66     QDialPrivate()
67     {
68         wrapping = false;
69         tracking = true;
70         doNotEmit = false;
71         target = qreal(3.7);
72     }
73 
74     qreal target;
75     uint showNotches : 1;
76     uint wrapping : 1;
77     uint doNotEmit : 1;
78 
79     int valueFromPoint(const QPoint &) const;
80     double angle(const QPoint &, const QPoint &) const;
81     void init();
82     virtual int bound(int val) const override;
83 };
84 
init()85 void QDialPrivate::init()
86 {
87     Q_Q(QDial);
88     showNotches = false;
89     q->setFocusPolicy(Qt::WheelFocus);
90 }
91 
bound(int val) const92 int QDialPrivate::bound(int val) const
93 {
94     if (wrapping) {
95         if ((val >= minimum) && (val <= maximum))
96             return val;
97         val = minimum + ((val - minimum) % (maximum - minimum));
98         if (val < minimum)
99             val += maximum - minimum;
100         return val;
101     } else {
102         return QAbstractSliderPrivate::bound(val);
103     }
104 }
105 
106 /*!
107     Initialize \a option with the values from this QDial. This method
108     is useful for subclasses when they need a QStyleOptionSlider, but don't want
109     to fill in all the information themselves.
110 
111     \sa QStyleOption::initFrom()
112 */
initStyleOption(QStyleOptionSlider * option) const113 void QDial::initStyleOption(QStyleOptionSlider *option) const
114 {
115     if (!option)
116         return;
117 
118     Q_D(const QDial);
119     option->initFrom(this);
120     option->minimum = d->minimum;
121     option->maximum = d->maximum;
122     option->sliderPosition = d->position;
123     option->sliderValue = d->value;
124     option->singleStep = d->singleStep;
125     option->pageStep = d->pageStep;
126     option->upsideDown = !d->invertedAppearance;
127     option->notchTarget = d->target;
128     option->dialWrapping = d->wrapping;
129     option->subControls = QStyle::SC_All;
130     option->activeSubControls = QStyle::SC_None;
131     if (!d->showNotches) {
132         option->subControls &= ~QStyle::SC_DialTickmarks;
133         option->tickPosition = QSlider::TicksAbove;
134     } else {
135         option->tickPosition = QSlider::NoTicks;
136     }
137     option->tickInterval = notchSize();
138 }
139 
valueFromPoint(const QPoint & p) const140 int QDialPrivate::valueFromPoint(const QPoint &p) const
141 {
142     Q_Q(const QDial);
143     double yy = q->height()/2.0 - p.y();
144     double xx = p.x() - q->width()/2.0;
145     double a = (xx || yy) ? std::atan2(yy, xx) : 0;
146 
147     if (a < Q_PI / -2)
148         a = a + Q_PI * 2;
149 
150     int dist = 0;
151     int minv = minimum, maxv = maximum;
152 
153     if (minimum < 0) {
154         dist = -minimum;
155         minv = 0;
156         maxv = maximum + dist;
157     }
158 
159     int r = maxv - minv;
160     int v;
161     if (wrapping)
162         v =  (int)(0.5 + minv + r * (Q_PI * 3 / 2 - a) / (2 * Q_PI));
163     else
164         v =  (int)(0.5 + minv + r* (Q_PI * 4 / 3 - a) / (Q_PI * 10 / 6));
165 
166     if (dist > 0)
167         v -= dist;
168 
169     return !invertedAppearance ? bound(v) : maximum - bound(v);
170 }
171 
172 /*!
173     \class QDial
174 
175     \brief The QDial class provides a rounded range control (like a speedometer or potentiometer).
176 
177     \ingroup basicwidgets
178     \inmodule QtWidgets
179 
180     \image windows-dial.png
181 
182     QDial is used when the user needs to control a value within a
183     program-definable range, and the range either wraps around
184     (for example, with angles measured from 0 to 359 degrees) or the
185     dialog layout needs a square widget.
186 
187     Since QDial inherits from QAbstractSlider, the dial behaves in
188     a similar way to a \l{QSlider}{slider}. When wrapping() is false
189     (the default setting) there is no real difference between a slider
190     and a dial. They both share the same signals, slots and member
191     functions. Which one you use depends on the expectations of
192     your users and on the type of application.
193 
194     The dial initially emits valueChanged() signals continuously while
195     the slider is being moved; you can make it emit the signal less
196     often by disabling the \l{QAbstractSlider::tracking} {tracking}
197     property. The sliderMoved() signal is emitted continuously even
198     when tracking is disabled.
199 
200     The dial also emits sliderPressed() and sliderReleased() signals
201     when the mouse button is pressed and released. Note that the
202     dial's value can change without these signals being emitted since
203     the keyboard and wheel can also be used to change the value.
204 
205     Unlike the slider, QDial attempts to draw a "nice" number of
206     notches rather than one per line step. If possible, the number of
207     notches drawn is one per line step, but if there aren't enough pixels
208     to draw every one, QDial will skip notches to try and draw a uniform
209     set (e.g. by drawing every second or third notch).
210 
211     Like the slider, the dial makes the QAbstractSlider function setValue()
212     available as a slot.
213 
214     The dial's keyboard interface is fairly simple: The
215     \uicontrol{left}/\uicontrol{up} and \uicontrol{right}/\uicontrol{down} arrow keys adjust
216     the dial's \l {QAbstractSlider::value} {value} by the defined
217     \l {QAbstractSlider::singleStep} {singleStep}, \uicontrol{Page Up} and
218     \uicontrol{Page Down} by the defined \l {QAbstractSlider::pageStep}
219     {pageStep}, and the \uicontrol Home and \uicontrol End keys set the value to
220     the defined \l {QAbstractSlider::minimum} {minimum} and
221     \l {QAbstractSlider::maximum} {maximum} values.
222 
223     If you are using the mouse wheel to adjust the dial, the increment
224     value is determined by the lesser value of
225     \l{QApplication::wheelScrollLines()} {wheelScrollLines} multipled
226     by \l {QAbstractSlider::singleStep} {singleStep}, and
227     \l {QAbstractSlider::pageStep} {pageStep}.
228 
229     \sa QScrollBar, QSpinBox, QSlider, {fowler}{GUI Design Handbook: Slider}, {Sliders Example}
230 */
231 
232 /*!
233     Constructs a dial.
234 
235     The \a parent argument is sent to the QAbstractSlider constructor.
236 */
QDial(QWidget * parent)237 QDial::QDial(QWidget *parent)
238     : QAbstractSlider(*new QDialPrivate, parent)
239 {
240     Q_D(QDial);
241     d->init();
242 }
243 
244 /*!
245     Destroys the dial.
246 */
~QDial()247 QDial::~QDial()
248 {
249 }
250 
251 /*! \reimp */
resizeEvent(QResizeEvent * e)252 void QDial::resizeEvent(QResizeEvent *e)
253 {
254     QWidget::resizeEvent(e);
255 }
256 
257 /*!
258   \reimp
259 */
260 
paintEvent(QPaintEvent *)261 void QDial::paintEvent(QPaintEvent *)
262 {
263     QStylePainter p(this);
264     QStyleOptionSlider option;
265     initStyleOption(&option);
266     p.drawComplexControl(QStyle::CC_Dial, option);
267 }
268 
269 /*!
270   \reimp
271 */
272 
mousePressEvent(QMouseEvent * e)273 void QDial::mousePressEvent(QMouseEvent *e)
274 {
275     Q_D(QDial);
276     if (d->maximum == d->minimum ||
277         (e->button() != Qt::LeftButton)  ||
278         (e->buttons() ^ e->button())) {
279         e->ignore();
280         return;
281     }
282     e->accept();
283     setSliderPosition(d->valueFromPoint(e->pos()));
284     // ### This isn't quite right,
285     // we should be doing a hit test and only setting this if it's
286     // the actual dial thingie (similar to what QSlider does), but we have no
287     // subControls for QDial.
288     setSliderDown(true);
289 }
290 
291 
292 /*!
293   \reimp
294 */
295 
mouseReleaseEvent(QMouseEvent * e)296 void QDial::mouseReleaseEvent(QMouseEvent * e)
297 {
298     Q_D(QDial);
299     if (e->buttons() & (~e->button()) ||
300        (e->button() != Qt::LeftButton)) {
301         e->ignore();
302         return;
303     }
304     e->accept();
305     setValue(d->valueFromPoint(e->pos()));
306     setSliderDown(false);
307 }
308 
309 
310 /*!
311   \reimp
312 */
313 
mouseMoveEvent(QMouseEvent * e)314 void QDial::mouseMoveEvent(QMouseEvent * e)
315 {
316     Q_D(QDial);
317     if (!(e->buttons() & Qt::LeftButton)) {
318         e->ignore();
319         return;
320     }
321     e->accept();
322     d->doNotEmit = true;
323     setSliderPosition(d->valueFromPoint(e->pos()));
324     d->doNotEmit = false;
325 }
326 
327 
328 /*!
329     \reimp
330 */
331 
sliderChange(SliderChange change)332 void QDial::sliderChange(SliderChange change)
333 {
334     QAbstractSlider::sliderChange(change);
335 }
336 
setWrapping(bool enable)337 void QDial::setWrapping(bool enable)
338 {
339     Q_D(QDial);
340     if (d->wrapping == enable)
341         return;
342     d->wrapping = enable;
343     update();
344 }
345 
346 
347 /*!
348     \property QDial::wrapping
349     \brief whether wrapping is enabled
350 
351     If true, wrapping is enabled; otherwise some space is inserted at the bottom
352     of the dial to separate the ends of the range of valid values.
353 
354     If enabled, the arrow can be oriented at any angle on the dial. If disabled,
355     the arrow will be restricted to the upper part of the dial; if it is rotated
356     into the space at the bottom of the dial, it will be clamped to the closest
357     end of the valid range of values.
358 
359     By default this property is \c false.
360 */
361 
wrapping() const362 bool QDial::wrapping() const
363 {
364     Q_D(const QDial);
365     return d->wrapping;
366 }
367 
368 
369 /*!
370     \property QDial::notchSize
371     \brief the current notch size
372 
373     The notch size is in range control units, not pixels, and if
374     possible it is a multiple of singleStep() that results in an
375     on-screen notch size near notchTarget().
376 
377     By default, this property has a value of 1.
378 
379     \sa notchTarget(), singleStep()
380 */
381 
notchSize() const382 int QDial::notchSize() const
383 {
384     Q_D(const QDial);
385     // radius of the arc
386     int r = qMin(width(), height())/2;
387     // length of the whole arc
388     int l = (int)(r * (d->wrapping ? 6 : 5) * Q_PI / 6);
389     // length of the arc from minValue() to minValue()+pageStep()
390     if (d->maximum > d->minimum + d->pageStep)
391         l = (int)(0.5 + l * d->pageStep / (d->maximum - d->minimum));
392     // length of a singleStep arc
393     l = l * d->singleStep / (d->pageStep ? d->pageStep : 1);
394     if (l < 1)
395         l = 1;
396     // how many times singleStep can be draw in d->target pixels
397     l = (int)(0.5 + d->target / l);
398     // we want notchSize() to be a non-zero multiple of lineStep()
399     if (!l)
400         l = 1;
401     return d->singleStep * l;
402 }
403 
setNotchTarget(double target)404 void QDial::setNotchTarget(double target)
405 {
406     Q_D(QDial);
407     d->target = target;
408     update();
409 }
410 
411 /*!
412     \property QDial::notchTarget
413     \brief the target number of pixels between notches
414 
415     The notch target is the number of pixels QDial attempts to put
416     between each notch.
417 
418     The actual size may differ from the target size.
419 
420     The default notch target is 3.7 pixels.
421 */
notchTarget() const422 qreal QDial::notchTarget() const
423 {
424     Q_D(const QDial);
425     return d->target;
426 }
427 
428 
setNotchesVisible(bool visible)429 void QDial::setNotchesVisible(bool visible)
430 {
431     Q_D(QDial);
432     d->showNotches = visible;
433     update();
434 }
435 
436 /*!
437     \property QDial::notchesVisible
438     \brief whether the notches are shown
439 
440     If the property is \c true, a series of notches are drawn around the dial
441     to indicate the range of values available; otherwise no notches are
442     shown.
443 
444     By default, this property is disabled.
445 */
notchesVisible() const446 bool QDial::notchesVisible() const
447 {
448     Q_D(const QDial);
449     return d->showNotches;
450 }
451 
452 /*!
453   \reimp
454 */
455 
minimumSizeHint() const456 QSize QDial::minimumSizeHint() const
457 {
458     return QSize(50, 50);
459 }
460 
461 /*!
462   \reimp
463 */
464 
sizeHint() const465 QSize QDial::sizeHint() const
466 {
467     return QSize(100, 100).expandedTo(QApplication::globalStrut());
468 }
469 
470 /*!
471   \reimp
472 */
event(QEvent * e)473 bool QDial::event(QEvent *e)
474 {
475     return QAbstractSlider::event(e);
476 }
477 
478 QT_END_NAMESPACE
479 
480 #include "moc_qdial.cpp"
481