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
18QT_BEGIN_NAMESPACE
19
20class QSliderPrivate : public QAbstractSliderPrivate
21{
22 Q_DECLARE_PUBLIC(QSlider)
23public:
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
39void 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
55void 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
65int 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
87inline 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*/
99void 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
132bool 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
146QStyle::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*/
253QSlider::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
264QSlider::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*/
275QSlider::~QSlider()
276{
277}
278
279/*!
280 \reimp
281*/
282void 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
300bool 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*/
324void 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*/
386void 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*/
402void 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*/
424QSize 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*/
447QSize 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
472void QSlider::setTickPosition(TickPosition position)
473{
474 Q_D(QSlider);
475 d->tickPosition = position;
476 d->resetLayoutItemMargins();
477 update();
478 updateGeometry();
479}
480
481QSlider::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
498void QSlider::setTickInterval(int ts)
499{
500 d_func()->tickInterval = qMax(a: 0, b: ts);
501 update();
502}
503
504int QSlider::tickInterval() const
505{
506 return d_func()->tickInterval;
507}
508
509Q_WIDGETS_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider)
510{
511 QStyleOptionSlider sliderOption;
512 slider->initStyleOption(option: &sliderOption);
513 return sliderOption;
514}
515
516QT_END_NAMESPACE
517
518#include "moc_qslider.cpp"
519

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