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 
53 QT_BEGIN_NAMESPACE
54 
55 class QSliderPrivate : public QAbstractSliderPrivate
56 {
57     Q_DECLARE_PUBLIC(QSlider)
58 public:
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 
init()74 void 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 
resetLayoutItemMargins()90 void QSliderPrivate::resetLayoutItemMargins()
91 {
92     Q_Q(QSlider);
93     QStyleOptionSlider opt;
94     q->initStyleOption(&opt);
95     setLayoutItemMargins(QStyle::SE_SliderLayoutItem, &opt);
96 }
97 
pixelPosToRangeValue(int pos) const98 int 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 
pick(const QPoint & pt) const120 inline 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 */
initStyleOption(QStyleOptionSlider * option) const132 void 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 
updateHoverControl(const QPoint & pos)158 bool 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 
newHoverControl(const QPoint & pos)172 QStyle::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 */
QSlider(QWidget * parent)279 QSlider::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 
QSlider(Qt::Orientation orientation,QWidget * parent)290 QSlider::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 */
~QSlider()301 QSlider::~QSlider()
302 {
303 }
304 
305 /*!
306     \reimp
307 */
paintEvent(QPaintEvent *)308 void 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 
event(QEvent * event)332 bool 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 */
mousePressEvent(QMouseEvent * ev)356 void 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 */
mouseMoveEvent(QMouseEvent * ev)418 void 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 */
mouseReleaseEvent(QMouseEvent * ev)436 void 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 */
sizeHint() const458 QSize 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 */
minimumSizeHint() const481 QSize 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 
setTickPosition(TickPosition position)506 void QSlider::setTickPosition(TickPosition position)
507 {
508     Q_D(QSlider);
509     d->tickPosition = position;
510     d->resetLayoutItemMargins();
511     update();
512     updateGeometry();
513 }
514 
tickPosition() const515 QSlider::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 
setTickInterval(int ts)532 void QSlider::setTickInterval(int ts)
533 {
534     d_func()->tickInterval = qMax(0, ts);
535     update();
536 }
537 
tickInterval() const538 int QSlider::tickInterval() const
539 {
540     return d_func()->tickInterval;
541 }
542 
qt_qsliderStyleOption(QSlider * slider)543 Q_WIDGETS_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider)
544 {
545     QStyleOptionSlider sliderOption;
546     slider->initStyleOption(&sliderOption);
547     return sliderOption;
548 }
549 
550 QT_END_NAMESPACE
551 
552 #include "moc_qslider.cpp"
553