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
60QT_BEGIN_NAMESPACE
61
62class QDialPrivate : public QAbstractSliderPrivate
63{
64 Q_DECLARE_PUBLIC(QDial)
65public:
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
85void QDialPrivate::init()
86{
87 Q_Q(QDial);
88 showNotches = false;
89 q->setFocusPolicy(Qt::WheelFocus);
90}
91
92int 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*/
113void 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
140int 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*/
237QDial::QDial(QWidget *parent)
238 : QAbstractSlider(*new QDialPrivate, parent)
239{
240 Q_D(QDial);
241 d->init();
242}
243
244/*!
245 Destroys the dial.
246*/
247QDial::~QDial()
248{
249}
250
251/*! \reimp */
252void QDial::resizeEvent(QResizeEvent *e)
253{
254 QWidget::resizeEvent(e);
255}
256
257/*!
258 \reimp
259*/
260
261void 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
273void 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->position().toPoint()));
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
296void 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->position().toPoint()));
306 setSliderDown(false);
307}
308
309
310/*!
311 \reimp
312*/
313
314void 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->position().toPoint()));
324 d->doNotEmit = false;
325}
326
327
328/*!
329 \reimp
330*/
331
332void QDial::sliderChange(SliderChange change)
333{
334 QAbstractSlider::sliderChange(change);
335}
336
337void 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
362bool 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
382int 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
404void 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*/
422qreal QDial::notchTarget() const
423{
424 Q_D(const QDial);
425 return d->target;
426}
427
428
429void 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*/
446bool QDial::notchesVisible() const
447{
448 Q_D(const QDial);
449 return d->showNotches;
450}
451
452/*!
453 \reimp
454*/
455
456QSize QDial::minimumSizeHint() const
457{
458 return QSize(50, 50);
459}
460
461/*!
462 \reimp
463*/
464
465QSize QDial::sizeHint() const
466{
467 return QSize(100, 100);
468}
469
470/*!
471 \reimp
472*/
473bool QDial::event(QEvent *e)
474{
475 return QAbstractSlider::event(e);
476}
477
478QT_END_NAMESPACE
479
480#include "moc_qdial.cpp"
481