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 | |
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(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, on: false); |
87 | resetLayoutItemMargins(); |
88 | } |
89 | |
90 | void QSliderPrivate::resetLayoutItemMargins() |
91 | { |
92 | Q_Q(QSlider); |
93 | QStyleOptionSlider opt; |
94 | q->initStyleOption(option: &opt); |
95 | setLayoutItemMargins(element: QStyle::SE_SliderLayoutItem, opt: &opt); |
96 | } |
97 | |
98 | int QSliderPrivate::pixelPosToRangeValue(int pos) const |
99 | { |
100 | Q_Q(const QSlider); |
101 | QStyleOptionSlider opt; |
102 | q->initStyleOption(option: &opt); |
103 | QRect gr = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderGroove, widget: q); |
104 | QRect sr = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: 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(min: minimum, max: maximum, pos: pos - sliderMin, |
117 | space: sliderMax - sliderMin, upsideDown: opt.upsideDown); |
118 | } |
119 | |
120 | 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 | */ |
132 | void QSlider::initStyleOption(QStyleOptionSlider *option) const |
133 | { |
134 | if (!option) |
135 | return; |
136 | |
137 | Q_D(const QSlider); |
138 | option->initFrom(w: 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 | |
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(attribute: 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 | |
172 | QStyle::SubControl QSliderPrivate::newHoverControl(const QPoint &pos) |
173 | { |
174 | Q_Q(QSlider); |
175 | QStyleOptionSlider opt; |
176 | q->initStyleOption(option: &opt); |
177 | opt.subControls = QStyle::SC_All; |
178 | QRect handleRect = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: q); |
179 | QRect grooveRect = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderGroove, widget: q); |
180 | QRect tickmarksRect = q->style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderTickmarks, widget: q); |
181 | |
182 | if (handleRect.contains(p: pos)) { |
183 | hoverRect = handleRect; |
184 | hoverControl = QStyle::SC_SliderHandle; |
185 | } else if (grooveRect.contains(p: pos)) { |
186 | hoverRect = grooveRect; |
187 | hoverControl = QStyle::SC_SliderGroove; |
188 | } else if (tickmarksRect.contains(p: 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 | */ |
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 | |
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 | */ |
301 | QSlider::~QSlider() |
302 | { |
303 | } |
304 | |
305 | /*! |
306 | \reimp |
307 | */ |
308 | void QSlider::paintEvent(QPaintEvent *) |
309 | { |
310 | Q_D(QSlider); |
311 | QPainter p(this); |
312 | QStyleOptionSlider opt; |
313 | initStyleOption(option: &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(cc: QStyle::CC_Slider, opt: &opt, p: &p, widget: this); |
326 | } |
327 | |
328 | /*! |
329 | \reimp |
330 | */ |
331 | |
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(pos: 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(e: event); |
351 | } |
352 | |
353 | /*! |
354 | \reimp |
355 | */ |
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(stylehint: QStyle::SH_Slider_AbsoluteSetButtons)) == ev->button()) { |
369 | QStyleOptionSlider opt; |
370 | initStyleOption(option: &opt); |
371 | const QRect sliderRect = style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: 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(pos: d->pick(pt: ev->pos() - center))); |
376 | triggerAction(action: SliderMove); |
377 | setRepeatAction(action: SliderNoAction); |
378 | d->pressedControl = QStyle::SC_SliderHandle; |
379 | update(); |
380 | } else if ((ev->button() & style()->styleHint(stylehint: QStyle::SH_Slider_PageSetButtons)) == ev->button()) { |
381 | QStyleOptionSlider opt; |
382 | initStyleOption(option: &opt); |
383 | d->pressedControl = style()->hitTestComplexControl(cc: QStyle::CC_Slider, |
384 | opt: &opt, pt: ev->pos(), widget: this); |
385 | SliderAction action = SliderNoAction; |
386 | if (d->pressedControl == QStyle::SC_SliderGroove) { |
387 | const QRect sliderRect = style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: this); |
388 | int pressValue = d->pixelPosToRangeValue(pos: d->pick(pt: 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(option: &opt); |
407 | setRepeatAction(action: SliderNoAction); |
408 | QRect sr = style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: QStyle::SC_SliderHandle, widget: this); |
409 | d->clickOffset = d->pick(pt: ev->pos() - sr.topLeft()); |
410 | update(sr); |
411 | setSliderDown(true); |
412 | } |
413 | } |
414 | |
415 | /*! |
416 | \reimp |
417 | */ |
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(pos: d->pick(pt: ev->pos()) - d->clickOffset); |
427 | QStyleOptionSlider opt; |
428 | initStyleOption(option: &opt); |
429 | setSliderPosition(newPosition); |
430 | } |
431 | |
432 | |
433 | /*! |
434 | \reimp |
435 | */ |
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(action: SliderNoAction); |
447 | if (oldPressed == QStyle::SC_SliderHandle) |
448 | setSliderDown(false); |
449 | QStyleOptionSlider opt; |
450 | initStyleOption(option: &opt); |
451 | opt.subControls = oldPressed; |
452 | update(style()->subControlRect(cc: QStyle::CC_Slider, opt: &opt, sc: oldPressed, widget: this)); |
453 | } |
454 | |
455 | /*! |
456 | \reimp |
457 | */ |
458 | QSize QSlider::sizeHint() const |
459 | { |
460 | Q_D(const QSlider); |
461 | ensurePolished(); |
462 | const int SliderLength = 84, TickSpace = 5; |
463 | QStyleOptionSlider opt; |
464 | initStyleOption(option: &opt); |
465 | int thick = style()->pixelMetric(metric: QStyle::PM_SliderThickness, option: &opt, widget: 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(ct: QStyle::CT_Slider, opt: &opt, contentsSize: QSize(w, h), w: this).expandedTo(otherSize: QApplication::globalStrut()); |
476 | } |
477 | |
478 | /*! |
479 | \reimp |
480 | */ |
481 | QSize QSlider::minimumSizeHint() const |
482 | { |
483 | Q_D(const QSlider); |
484 | QSize s = sizeHint(); |
485 | QStyleOptionSlider opt; |
486 | initStyleOption(option: &opt); |
487 | int length = style()->pixelMetric(metric: QStyle::PM_SliderLength, option: &opt, widget: 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 | |
506 | void QSlider::setTickPosition(TickPosition position) |
507 | { |
508 | Q_D(QSlider); |
509 | d->tickPosition = position; |
510 | d->resetLayoutItemMargins(); |
511 | update(); |
512 | updateGeometry(); |
513 | } |
514 | |
515 | 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 | |
532 | void QSlider::setTickInterval(int ts) |
533 | { |
534 | d_func()->tickInterval = qMax(a: 0, b: ts); |
535 | update(); |
536 | } |
537 | |
538 | int QSlider::tickInterval() const |
539 | { |
540 | return d_func()->tickInterval; |
541 | } |
542 | |
543 | Q_WIDGETS_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider) |
544 | { |
545 | QStyleOptionSlider sliderOption; |
546 | slider->initStyleOption(option: &sliderOption); |
547 | return sliderOption; |
548 | } |
549 | |
550 | QT_END_NAMESPACE |
551 | |
552 | #include "moc_qslider.cpp" |
553 | |