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

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