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 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | class QDialPrivate : public QAbstractSliderPrivate |
27 | { |
28 | Q_DECLARE_PUBLIC(QDial) |
29 | public: |
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 | |
49 | void QDialPrivate::init() |
50 | { |
51 | Q_Q(QDial); |
52 | showNotches = false; |
53 | q->setFocusPolicy(Qt::WheelFocus); |
54 | } |
55 | |
56 | int 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 | */ |
77 | void 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 | |
104 | int 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 | */ |
201 | QDial::QDial(QWidget *parent) |
202 | : QAbstractSlider(*new QDialPrivate, parent) |
203 | { |
204 | Q_D(QDial); |
205 | d->init(); |
206 | } |
207 | |
208 | /*! |
209 | Destroys the dial. |
210 | */ |
211 | QDial::~QDial() |
212 | { |
213 | } |
214 | |
215 | /*! \reimp */ |
216 | void QDial::resizeEvent(QResizeEvent *e) |
217 | { |
218 | QWidget::resizeEvent(event: e); |
219 | } |
220 | |
221 | /*! |
222 | \reimp |
223 | */ |
224 | |
225 | void 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 | |
237 | void 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 | |
260 | void 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 | |
278 | void 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 | |
296 | void QDial::sliderChange(SliderChange change) |
297 | { |
298 | QAbstractSlider::sliderChange(change); |
299 | } |
300 | |
301 | void 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 | |
326 | bool 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 | |
344 | int 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 | |
362 | void 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 | */ |
380 | qreal QDial::notchTarget() const |
381 | { |
382 | Q_D(const QDial); |
383 | return d->target; |
384 | } |
385 | |
386 | |
387 | void 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 | */ |
404 | bool QDial::notchesVisible() const |
405 | { |
406 | Q_D(const QDial); |
407 | return d->showNotches; |
408 | } |
409 | |
410 | /*! |
411 | \reimp |
412 | */ |
413 | |
414 | QSize QDial::minimumSizeHint() const |
415 | { |
416 | return QSize(50, 50); |
417 | } |
418 | |
419 | /*! |
420 | \reimp |
421 | */ |
422 | |
423 | QSize QDial::sizeHint() const |
424 | { |
425 | return QSize(100, 100); |
426 | } |
427 | |
428 | /*! |
429 | \reimp |
430 | */ |
431 | bool QDial::event(QEvent *e) |
432 | { |
433 | return QAbstractSlider::event(e); |
434 | } |
435 | |
436 | QT_END_NAMESPACE |
437 | |
438 | #include "moc_qdial.cpp" |
439 | |