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 "qslider.h" |
5 | #if QT_CONFIG(accessibility) |
6 | #include "qaccessible.h" |
7 | #endif |
8 | #include "qapplication.h" |
9 | #include "qevent.h" |
10 | #include "qpainter.h" |
11 | #include "qstyle.h" |
12 | #include "qstyleoption.h" |
13 | #include "qstylepainter.h" |
14 | #include "private/qapplication_p.h" |
15 | #include "private/qabstractslider_p.h" |
16 | #include "qdebug.h" |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | class QSliderPrivate : public QAbstractSliderPrivate |
21 | { |
22 | Q_DECLARE_PUBLIC(QSlider) |
23 | public: |
24 | QStyle::SubControl pressedControl; |
25 | int tickInterval; |
26 | QSlider::TickPosition tickPosition; |
27 | int clickOffset; |
28 | void init(); |
29 | void resetLayoutItemMargins(); |
30 | int pixelPosToRangeValue(int pos) const; |
31 | inline int pick(const QPoint &pt) const; |
32 | |
33 | QStyle::SubControl newHoverControl(const QPoint &pos); |
34 | bool updateHoverControl(const QPoint &pos); |
35 | QStyle::SubControl hoverControl; |
36 | QRect hoverRect; |
37 | }; |
38 | |
39 | void QSliderPrivate::init() |
40 | { |
41 | Q_Q(QSlider); |
42 | pressedControl = QStyle::SC_None; |
43 | tickInterval = 0; |
44 | tickPosition = QSlider::NoTicks; |
45 | hoverControl = QStyle::SC_None; |
46 | q->setFocusPolicy(Qt::FocusPolicy(q->style()->styleHint(stylehint: QStyle::SH_Button_FocusPolicy))); |
47 | QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::Slider); |
48 | if (orientation == Qt::Vertical) |
49 | sp.transpose(); |
50 | q->setSizePolicy(sp); |
51 | q->setAttribute(Qt::WA_WState_OwnSizePolicy, on: false); |
52 | resetLayoutItemMargins(); |
53 | } |
54 | |
55 | void QSliderPrivate::resetLayoutItemMargins() |
56 | { |
57 | Q_Q(QSlider); |
58 | QStyleOptionSlider opt; |
59 | // ### This is (also) reached from the ctor which is unfortunate since a possible |
60 | // ### re-implementation of initStyleOption is then not called. |
61 | q->initStyleOption(option: &opt); |
62 | setLayoutItemMargins(element: QStyle::SE_SliderLayoutItem, opt: &opt); |
63 | } |
64 | |
65 | int QSliderPrivate::pixelPosToRangeValue(int pos) const |
66 | { |
67 | Q_Q(const QSlider); |
68 | QStyleOptionSlider opt; |
69 | q->initStyleOption(option: &opt); |
70 | QRect gr = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderGroove, widget: q); |
71 | QRect sr = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: q); |
72 | int sliderMin, sliderMax, sliderLength; |
73 | |
74 | if (orientation == Qt::Horizontal) { |
75 | sliderLength = sr.width(); |
76 | sliderMin = gr.x(); |
77 | sliderMax = gr.right() - sliderLength + 1; |
78 | } else { |
79 | sliderLength = sr.height(); |
80 | sliderMin = gr.y(); |
81 | sliderMax = gr.bottom() - sliderLength + 1; |
82 | } |
83 | return QStyle::sliderValueFromPosition(min: minimum, max: maximum, pos: pos - sliderMin, |
84 | space: sliderMax - sliderMin, upsideDown: opt.upsideDown); |
85 | } |
86 | |
87 | inline int QSliderPrivate::pick(const QPoint &pt) const |
88 | { |
89 | return orientation == Qt::Horizontal ? pt.x() : pt.y(); |
90 | } |
91 | |
92 | /*! |
93 | Initialize \a option with the values from this QSlider. This method |
94 | is useful for subclasses when they need a QStyleOptionSlider, but don't want |
95 | to fill in all the information themselves. |
96 | |
97 | \sa QStyleOption::initFrom() |
98 | */ |
99 | void QSlider::initStyleOption(QStyleOptionSlider *option) const |
100 | { |
101 | if (!option) |
102 | return; |
103 | |
104 | Q_D(const QSlider); |
105 | option->initFrom(w: this); |
106 | option->subControls = QStyle::SC_None; |
107 | option->activeSubControls = QStyle::SC_None; |
108 | option->orientation = d->orientation; |
109 | option->maximum = d->maximum; |
110 | option->minimum = d->minimum; |
111 | option->tickPosition = (QSlider::TickPosition)d->tickPosition; |
112 | option->tickInterval = d->tickInterval; |
113 | option->upsideDown = (d->orientation == Qt::Horizontal) ? |
114 | (d->invertedAppearance != (option->direction == Qt::RightToLeft)) |
115 | : (!d->invertedAppearance); |
116 | option->direction = Qt::LeftToRight; // we use the upsideDown option instead |
117 | option->sliderPosition = d->position; |
118 | option->sliderValue = d->value; |
119 | option->singleStep = d->singleStep; |
120 | option->pageStep = d->pageStep; |
121 | if (d->orientation == Qt::Horizontal) |
122 | option->state |= QStyle::State_Horizontal; |
123 | |
124 | if (d->pressedControl) { |
125 | option->activeSubControls = d->pressedControl; |
126 | option->state |= QStyle::State_Sunken; |
127 | } else { |
128 | option->activeSubControls = d->hoverControl; |
129 | } |
130 | } |
131 | |
132 | bool QSliderPrivate::updateHoverControl(const QPoint &pos) |
133 | { |
134 | Q_Q(QSlider); |
135 | QRect lastHoverRect = hoverRect; |
136 | QStyle::SubControl lastHoverControl = hoverControl; |
137 | bool doesHover = q->testAttribute(attribute: Qt::WA_Hover); |
138 | if (lastHoverControl != newHoverControl(pos) && doesHover) { |
139 | q->update(lastHoverRect); |
140 | q->update(hoverRect); |
141 | return true; |
142 | } |
143 | return !doesHover; |
144 | } |
145 | |
146 | QStyle::SubControl QSliderPrivate::newHoverControl(const QPoint &pos) |
147 | { |
148 | Q_Q(QSlider); |
149 | QStyleOptionSlider opt; |
150 | q->initStyleOption(option: &opt); |
151 | opt.subControls = QStyle::SC_All; |
152 | QRect handleRect = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: q); |
153 | QRect grooveRect = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderGroove, widget: q); |
154 | QRect tickmarksRect = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderTickmarks, widget: q); |
155 | |
156 | if (handleRect.contains(p: pos)) { |
157 | hoverRect = handleRect; |
158 | hoverControl = QStyle::SC_SliderHandle; |
159 | } else if (grooveRect.contains(p: pos)) { |
160 | hoverRect = grooveRect; |
161 | hoverControl = QStyle::SC_SliderGroove; |
162 | } else if (tickmarksRect.contains(p: pos)) { |
163 | hoverRect = tickmarksRect; |
164 | hoverControl = QStyle::SC_SliderTickmarks; |
165 | } else { |
166 | hoverRect = QRect(); |
167 | hoverControl = QStyle::SC_None; |
168 | } |
169 | |
170 | return hoverControl; |
171 | } |
172 | |
173 | /*! |
174 | \class QSlider |
175 | \brief The QSlider widget provides a vertical or horizontal slider. |
176 | |
177 | \ingroup basicwidgets |
178 | \inmodule QtWidgets |
179 | |
180 | \image windows-slider.png |
181 | |
182 | The slider is the classic widget for controlling a bounded value. |
183 | It lets the user move a slider handle along a horizontal or vertical |
184 | groove and translates the handle's position into an integer value |
185 | within the legal range. |
186 | |
187 | QSlider has very few of its own functions; most of the functionality is in |
188 | QAbstractSlider. The most useful functions are setValue() to set |
189 | the slider directly to some value; triggerAction() to simulate |
190 | the effects of clicking (useful for shortcut keys); |
191 | setSingleStep(), setPageStep() to set the steps; and setMinimum() |
192 | and setMaximum() to define the range of the scroll bar. |
193 | |
194 | QSlider provides methods for controlling tickmarks. You can use |
195 | setTickPosition() to indicate where you want the tickmarks to be, |
196 | setTickInterval() to indicate how many of them you want. the |
197 | currently set tick position and interval can be queried using the |
198 | tickPosition() and tickInterval() functions, respectively. |
199 | |
200 | QSlider inherits a comprehensive set of signals: |
201 | \table |
202 | \header \li Signal \li Description |
203 | \row \li \l valueChanged() |
204 | \li Emitted when the slider's value has changed. The tracking() |
205 | determines whether this signal is emitted during user |
206 | interaction. |
207 | \row \li \l sliderPressed() |
208 | \li Emitted when the user starts to drag the slider. |
209 | \row \li \l sliderMoved() |
210 | \li Emitted when the user drags the slider. |
211 | \row \li \l sliderReleased() |
212 | \li Emitted when the user releases the slider. |
213 | \endtable |
214 | |
215 | QSlider only provides integer ranges. Note that although |
216 | QSlider handles very large numbers, it becomes difficult for users |
217 | to use a slider accurately for very large ranges. |
218 | |
219 | A slider accepts focus on Tab and provides both a mouse wheel and a |
220 | keyboard interface. The keyboard interface is the following: |
221 | |
222 | \list |
223 | \li Left/Right move a horizontal slider by one single step. |
224 | \li Up/Down move a vertical slider by one single step. |
225 | \li PageUp moves up one page. |
226 | \li PageDown moves down one page. |
227 | \li Home moves to the start (minimum). |
228 | \li End moves to the end (maximum). |
229 | \endlist |
230 | |
231 | \sa QScrollBar, QSpinBox, QDial, {Sliders Example} |
232 | */ |
233 | |
234 | |
235 | /*! |
236 | \enum QSlider::TickPosition |
237 | |
238 | This enum specifies where the tick marks are to be drawn relative |
239 | to the slider's groove and the handle the user moves. |
240 | |
241 | \value NoTicks Do not draw any tick marks. |
242 | \value TicksBothSides Draw tick marks on both sides of the groove. |
243 | \value TicksAbove Draw tick marks above the (horizontal) slider |
244 | \value TicksBelow Draw tick marks below the (horizontal) slider |
245 | \value TicksLeft Draw tick marks to the left of the (vertical) slider |
246 | \value TicksRight Draw tick marks to the right of the (vertical) slider |
247 | */ |
248 | |
249 | |
250 | /*! |
251 | Constructs a vertical slider with the given \a parent. |
252 | */ |
253 | QSlider::QSlider(QWidget *parent) |
254 | : QSlider(Qt::Vertical, parent) |
255 | { |
256 | } |
257 | |
258 | /*! |
259 | Constructs a slider with the given \a parent. The \a orientation |
260 | parameter determines whether the slider is horizontal or vertical; |
261 | the valid values are Qt::Vertical and Qt::Horizontal. |
262 | */ |
263 | |
264 | QSlider::QSlider(Qt::Orientation orientation, QWidget *parent) |
265 | : QAbstractSlider(*new QSliderPrivate, parent) |
266 | { |
267 | d_func()->orientation = orientation; |
268 | d_func()->init(); |
269 | } |
270 | |
271 | |
272 | /*! |
273 | Destroys this slider. |
274 | */ |
275 | QSlider::~QSlider() |
276 | { |
277 | } |
278 | |
279 | /*! |
280 | \reimp |
281 | */ |
282 | void QSlider::paintEvent(QPaintEvent *) |
283 | { |
284 | Q_D(QSlider); |
285 | QStylePainter p(this); |
286 | QStyleOptionSlider opt; |
287 | initStyleOption(option: &opt); |
288 | |
289 | opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; |
290 | if (d->tickPosition != NoTicks) |
291 | opt.subControls |= QStyle::SC_SliderTickmarks; |
292 | |
293 | p.drawComplexControl(cc: QStyle::CC_Slider, opt); |
294 | } |
295 | |
296 | /*! |
297 | \reimp |
298 | */ |
299 | |
300 | bool QSlider::event(QEvent *event) |
301 | { |
302 | Q_D(QSlider); |
303 | |
304 | switch(event->type()) { |
305 | case QEvent::HoverEnter: |
306 | case QEvent::HoverLeave: |
307 | case QEvent::HoverMove: |
308 | if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event)) |
309 | d->updateHoverControl(pos: he->position().toPoint()); |
310 | break; |
311 | case QEvent::StyleChange: |
312 | case QEvent::MacSizeChange: |
313 | d->resetLayoutItemMargins(); |
314 | break; |
315 | default: |
316 | break; |
317 | } |
318 | return QAbstractSlider::event(e: event); |
319 | } |
320 | |
321 | /*! |
322 | \reimp |
323 | */ |
324 | void QSlider::mousePressEvent(QMouseEvent *ev) |
325 | { |
326 | Q_D(QSlider); |
327 | if (d->maximum == d->minimum || (ev->buttons() ^ ev->button())) { |
328 | ev->ignore(); |
329 | return; |
330 | } |
331 | #ifdef QT_KEYPAD_NAVIGATION |
332 | if (QApplicationPrivate::keypadNavigationEnabled()) |
333 | setEditFocus(true); |
334 | #endif |
335 | ev->accept(); |
336 | if ((ev->button() & style()->styleHint(stylehint: QStyle::SH_Slider_AbsoluteSetButtons)) == ev->button()) { |
337 | QStyleOptionSlider opt; |
338 | initStyleOption(option: &opt); |
339 | const QRect sliderRect = style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: this); |
340 | const QPoint center = sliderRect.center() - sliderRect.topLeft(); |
341 | // to take half of the slider off for the setSliderPosition call we use the center - topLeft |
342 | |
343 | setSliderPosition(d->pixelPosToRangeValue(pos: d->pick(pt: ev->position().toPoint() - center))); |
344 | triggerAction(action: SliderMove); |
345 | setRepeatAction(action: SliderNoAction); |
346 | d->pressedControl = QStyle::SC_SliderHandle; |
347 | update(); |
348 | } else if ((ev->button() & style()->styleHint(stylehint: QStyle::SH_Slider_PageSetButtons)) == ev->button()) { |
349 | QStyleOptionSlider opt; |
350 | initStyleOption(option: &opt); |
351 | d->pressedControl = style()->hitTestComplexControl(cc: QStyle::CC_Slider, |
352 | opt: &opt, pt: ev->position().toPoint(), widget: this); |
353 | SliderAction action = SliderNoAction; |
354 | if (d->pressedControl == QStyle::SC_SliderGroove) { |
355 | const QRect sliderRect = style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: this); |
356 | int pressValue = d->pixelPosToRangeValue(pos: d->pick(pt: ev->position().toPoint() - sliderRect.center() + sliderRect.topLeft())); |
357 | d->pressValue = pressValue; |
358 | if (pressValue > d->value) |
359 | action = SliderPageStepAdd; |
360 | else if (pressValue < d->value) |
361 | action = SliderPageStepSub; |
362 | if (action) { |
363 | triggerAction(action); |
364 | setRepeatAction(action); |
365 | } |
366 | } |
367 | } else { |
368 | ev->ignore(); |
369 | return; |
370 | } |
371 | |
372 | if (d->pressedControl == QStyle::SC_SliderHandle) { |
373 | QStyleOptionSlider opt; |
374 | initStyleOption(option: &opt); |
375 | setRepeatAction(action: SliderNoAction); |
376 | QRect sr = style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: this); |
377 | d->clickOffset = d->pick(pt: ev->position().toPoint() - sr.topLeft()); |
378 | update(sr); |
379 | setSliderDown(true); |
380 | } |
381 | } |
382 | |
383 | /*! |
384 | \reimp |
385 | */ |
386 | void QSlider::mouseMoveEvent(QMouseEvent *ev) |
387 | { |
388 | Q_D(QSlider); |
389 | if (d->pressedControl != QStyle::SC_SliderHandle) { |
390 | ev->ignore(); |
391 | return; |
392 | } |
393 | ev->accept(); |
394 | int newPosition = d->pixelPosToRangeValue(pos: d->pick(pt: ev->position().toPoint()) - d->clickOffset); |
395 | setSliderPosition(newPosition); |
396 | } |
397 | |
398 | |
399 | /*! |
400 | \reimp |
401 | */ |
402 | void QSlider::mouseReleaseEvent(QMouseEvent *ev) |
403 | { |
404 | Q_D(QSlider); |
405 | if (d->pressedControl == QStyle::SC_None || ev->buttons()) { |
406 | ev->ignore(); |
407 | return; |
408 | } |
409 | ev->accept(); |
410 | QStyle::SubControl oldPressed = QStyle::SubControl(d->pressedControl); |
411 | d->pressedControl = QStyle::SC_None; |
412 | setRepeatAction(action: SliderNoAction); |
413 | if (oldPressed == QStyle::SC_SliderHandle) |
414 | setSliderDown(false); |
415 | QStyleOptionSlider opt; |
416 | initStyleOption(option: &opt); |
417 | opt.subControls = oldPressed; |
418 | update(style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: oldPressed, widget: this)); |
419 | } |
420 | |
421 | /*! |
422 | \reimp |
423 | */ |
424 | QSize QSlider::sizeHint() const |
425 | { |
426 | Q_D(const QSlider); |
427 | ensurePolished(); |
428 | const int SliderLength = 84, TickSpace = 5; |
429 | QStyleOptionSlider opt; |
430 | initStyleOption(option: &opt); |
431 | int thick = style()->pixelMetric(metric: QStyle::PM_SliderThickness, option: &opt, widget: this); |
432 | if (d->tickPosition & TicksAbove) |
433 | thick += TickSpace; |
434 | if (d->tickPosition & TicksBelow) |
435 | thick += TickSpace; |
436 | int w = thick, h = SliderLength; |
437 | if (d->orientation == Qt::Horizontal) { |
438 | w = SliderLength; |
439 | h = thick; |
440 | } |
441 | return style()->sizeFromContents(ct: QStyle::CT_Slider, opt: &opt, contentsSize: QSize(w, h), w: this); |
442 | } |
443 | |
444 | /*! |
445 | \reimp |
446 | */ |
447 | QSize QSlider::minimumSizeHint() const |
448 | { |
449 | Q_D(const QSlider); |
450 | QSize s = sizeHint(); |
451 | QStyleOptionSlider opt; |
452 | initStyleOption(option: &opt); |
453 | int length = style()->pixelMetric(metric: QStyle::PM_SliderLength, option: &opt, widget: this); |
454 | if (d->orientation == Qt::Horizontal) |
455 | s.setWidth(length); |
456 | else |
457 | s.setHeight(length); |
458 | return s; |
459 | } |
460 | |
461 | /*! |
462 | \property QSlider::tickPosition |
463 | \brief the tickmark position for this slider |
464 | |
465 | The valid values are described by the QSlider::TickPosition enum. |
466 | |
467 | The default value is \l QSlider::NoTicks. |
468 | |
469 | \sa tickInterval |
470 | */ |
471 | |
472 | void QSlider::setTickPosition(TickPosition position) |
473 | { |
474 | Q_D(QSlider); |
475 | d->tickPosition = position; |
476 | d->resetLayoutItemMargins(); |
477 | update(); |
478 | updateGeometry(); |
479 | } |
480 | |
481 | QSlider::TickPosition QSlider::tickPosition() const |
482 | { |
483 | return d_func()->tickPosition; |
484 | } |
485 | |
486 | /*! |
487 | \property QSlider::tickInterval |
488 | \brief the interval between tickmarks |
489 | |
490 | This is a value interval, not a pixel interval. If it is 0, the |
491 | slider will choose between singleStep and pageStep. |
492 | |
493 | The default value is 0. |
494 | |
495 | \sa tickPosition, singleStep, pageStep |
496 | */ |
497 | |
498 | void QSlider::setTickInterval(int ts) |
499 | { |
500 | d_func()->tickInterval = qMax(a: 0, b: ts); |
501 | update(); |
502 | } |
503 | |
504 | int QSlider::tickInterval() const |
505 | { |
506 | return d_func()->tickInterval; |
507 | } |
508 | |
509 | Q_WIDGETS_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider) |
510 | { |
511 | QStyleOptionSlider sliderOption; |
512 | slider->initStyleOption(option: &sliderOption); |
513 | return sliderOption; |
514 | } |
515 | |
516 | QT_END_NAMESPACE |
517 | |
518 | #include "moc_qslider.cpp" |
519 | |