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(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 | |
90 | void 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 | |
100 | int 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 | |
122 | inline 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 | */ |
134 | void 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 | |
160 | bool 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 | |
174 | QStyle::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 | */ |
281 | QSlider::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 | |
292 | QSlider::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 | */ |
303 | QSlider::~QSlider() |
304 | { |
305 | } |
306 | |
307 | /*! |
308 | \reimp |
309 | */ |
310 | void 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 | |
334 | bool 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 | */ |
358 | void 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 | */ |
420 | void 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 | */ |
438 | void 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 | */ |
460 | QSize 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 | */ |
483 | QSize 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 | |
508 | void QSlider::setTickPosition(TickPosition position) |
509 | { |
510 | Q_D(QSlider); |
511 | d->tickPosition = position; |
512 | d->resetLayoutItemMargins(); |
513 | update(); |
514 | updateGeometry(); |
515 | } |
516 | |
517 | QSlider::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 | |
534 | void QSlider::setTickInterval(int ts) |
535 | { |
536 | d_func()->tickInterval = qMax(0, ts); |
537 | update(); |
538 | } |
539 | |
540 | int QSlider::tickInterval() const |
541 | { |
542 | return d_func()->tickInterval; |
543 | } |
544 | |
545 | Q_WIDGETS_EXPORT QStyleOptionSlider qt_qsliderStyleOption(QSlider *slider) |
546 | { |
547 | QStyleOptionSlider sliderOption; |
548 | slider->initStyleOption(&sliderOption); |
549 | return sliderOption; |
550 | } |
551 | |
552 | QT_END_NAMESPACE |
553 | |
554 | #include "moc_qslider.cpp" |
555 | |