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