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