1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qapplication.h"
5#include "qcursor.h"
6#include "qevent.h"
7#include "qpainter.h"
8#include "qscrollbar.h"
9#include "qstyle.h"
10#include "qstyleoption.h"
11#include "qstylepainter.h"
12#if QT_CONFIG(menu)
13#include "qmenu.h"
14#endif
15#include <QtCore/qelapsedtimer.h>
16
17#if QT_CONFIG(accessibility)
18#include "qaccessible.h"
19#endif
20#include <limits.h>
21#include "qscrollbar_p.h"
22
23QT_BEGIN_NAMESPACE
24
25/*!
26 \class QScrollBar
27 \brief The QScrollBar widget provides a vertical or horizontal scroll bar.
28
29 \ingroup basicwidgets
30 \inmodule QtWidgets
31
32 A scroll bar is a control that enables the user to access parts of a
33 document that is larger than the widget used to display it. It provides
34 a visual indication of the user's current position within the document
35 and the amount of the document that is visible. Scroll bars are usually
36 equipped with other controls that enable more accurate navigation.
37 Qt displays scroll bars in a way that is appropriate for each platform.
38
39 If you need to provide a scrolling view onto another widget, it may be
40 more convenient to use the QScrollArea class because this provides a
41 viewport widget and scroll bars. QScrollBar is useful if you need to
42 implement similar functionality for specialized widgets using QAbstractScrollArea;
43 for example, if you decide to subclass QAbstractItemView.
44 For most other situations where a slider control is used to obtain a value
45 within a given range, the QSlider class may be more appropriate for your
46 needs.
47
48 \table
49 \row \li \image qscrollbar-picture.png
50 \li Scroll bars typically include four separate controls: a slider,
51 scroll arrows, and a page control.
52
53 \list
54 \li a. The slider provides a way to quickly go to any part of the
55 document, but does not support accurate navigation within large
56 documents.
57 \li b. The scroll arrows are push buttons which can be used to accurately
58 navigate to a particular place in a document. For a vertical scroll bar
59 connected to a text editor, these typically move the current position one
60 "line" up or down, and adjust the position of the slider by a small
61 amount. In editors and list boxes a "line" might mean one line of text;
62 in an image viewer it might mean 20 pixels.
63 \li c. The page control is the area over which the slider is dragged (the
64 scroll bar's background). Clicking here moves the scroll bar towards
65 the click by one "page". This value is usually the same as the length of
66 the slider.
67 \endlist
68 \endtable
69
70 Each scroll bar has a value that indicates how far the slider is from
71 the start of the scroll bar; this is obtained with value() and set
72 with setValue(). This value always lies within the range of values
73 defined for the scroll bar, from \l{QAbstractSlider::minimum()}{minimum()}
74 to \l{QAbstractSlider::minimum()}{maximum()} inclusive. The range of
75 acceptable values can be set with setMinimum() and setMaximum().
76 At the minimum value, the top edge of the slider (for a vertical scroll
77 bar) or left edge (for a horizontal scroll bar) will be at the top (or
78 left) end of the scroll bar. At the maximum value, the bottom (or right)
79 edge of the slider will be at the bottom (or right) end of the scroll bar.
80
81 The length of the slider is usually related to the value of the page step,
82 and typically represents the proportion of the document area shown in a
83 scrolling view. The page step is the amount that the value changes by
84 when the user presses the \uicontrol{Page Up} and \uicontrol{Page Down} keys, and is
85 set with setPageStep(). Smaller changes to the value defined by the
86 line step are made using the cursor keys, and this quantity is set with
87 \l{QAbstractSlider::}{setSingleStep()}.
88
89 Note that the range of values used is independent of the actual size
90 of the scroll bar widget. You do not need to take this into account when
91 you choose values for the range and the page step.
92
93 The range of values specified for the scroll bar are often determined
94 differently to those for a QSlider because the length of the slider
95 needs to be taken into account. If we have a document with 100 lines,
96 and we can only show 20 lines in a widget, we may wish to construct a
97 scroll bar with a page step of 20, a minimum value of 0, and a maximum
98 value of 80. This would give us a scroll bar with five "pages".
99
100 \table
101 \row \li \inlineimage qscrollbar-values.png
102 \li The relationship between a document length, the range of values used
103 in a scroll bar, and the page step is simple in many common situations.
104 The scroll bar's range of values is determined by subtracting a
105 chosen page step from some value representing the length of the document.
106 In such cases, the following equation is useful:
107 \e{document length} = maximum() - minimum() + pageStep().
108 \endtable
109
110 QScrollBar only provides integer ranges. Note that although
111 QScrollBar handles very large numbers, scroll bars on current
112 screens cannot usefully represent ranges above about 100,000 pixels.
113 Beyond that, it becomes difficult for the user to control the
114 slider using either the keyboard or the mouse, and the scroll
115 arrows will have limited use.
116
117 ScrollBar inherits a comprehensive set of signals from QAbstractSlider:
118 \list
119 \li \l{QAbstractSlider::valueChanged()}{valueChanged()} is emitted when the
120 scroll bar's value has changed. The tracking() determines whether this
121 signal is emitted during user interaction.
122 \li \l{QAbstractSlider::rangeChanged()}{rangeChanged()} is emitted when the
123 scroll bar's range of values has changed.
124 \li \l{QAbstractSlider::sliderPressed()}{sliderPressed()} is emitted when
125 the user starts to drag the slider.
126 \li \l{QAbstractSlider::sliderMoved()}{sliderMoved()} is emitted when the user
127 drags the slider.
128 \li \l{QAbstractSlider::sliderReleased()}{sliderReleased()} is emitted when
129 the user releases the slider.
130 \li \l{QAbstractSlider::actionTriggered()}{actionTriggered()} is emitted
131 when the scroll bar is changed by user interaction or via the
132 \l{QAbstractSlider::triggerAction()}{triggerAction()} function.
133 \endlist
134
135 A scroll bar can be controlled by the keyboard, but it has a
136 default focusPolicy() of Qt::NoFocus. Use setFocusPolicy() to
137 enable keyboard interaction with the scroll bar:
138 \list
139 \li Left/Right move a horizontal scroll bar by one single step.
140 \li Up/Down move a vertical scroll bar by one single step.
141 \li PageUp moves up one page.
142 \li PageDown moves down one page.
143 \li Home moves to the start (minimum).
144 \li End moves to the end (maximum).
145 \endlist
146
147 The slider itself can be controlled by using the
148 \l{QAbstractSlider::triggerAction()}{triggerAction()} function to simulate
149 user interaction with the scroll bar controls. This is useful if you have
150 many different widgets that use a common range of values.
151
152 Most GUI styles use the pageStep() value to calculate the size of the
153 slider.
154
155 \sa QScrollArea, QSlider, QDial, QSpinBox, {Sliders Example}
156*/
157
158bool QScrollBarPrivate::updateHoverControl(const QPoint &pos)
159{
160 Q_Q(QScrollBar);
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
172QStyle::SubControl QScrollBarPrivate::newHoverControl(const QPoint &pos)
173{
174 Q_Q(QScrollBar);
175 QStyleOptionSlider opt;
176 q->initStyleOption(option: &opt);
177 opt.subControls = QStyle::SC_All;
178 hoverControl = q->style()->hitTestComplexControl(cc: QStyle::CC_ScrollBar, opt: &opt, pt: pos, widget: q);
179 if (hoverControl == QStyle::SC_None)
180 hoverRect = QRect();
181 else
182 hoverRect = q->style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: hoverControl, widget: q);
183 return hoverControl;
184}
185
186void QScrollBarPrivate::setTransient(bool value)
187{
188 Q_Q(QScrollBar);
189 if (transient != value) {
190 transient = value;
191 if (q->isVisible()) {
192 QStyleOptionSlider opt;
193 q->initStyleOption(option: &opt);
194 if (q->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: q))
195 q->update();
196 } else if (!transient) {
197 q->show();
198 }
199 }
200}
201
202void QScrollBarPrivate::flash()
203{
204 Q_Q(QScrollBar);
205 QStyleOptionSlider opt;
206 q->initStyleOption(option: &opt);
207 if (!flashed && q->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: q)) {
208 flashed = true;
209 if (!q->isVisible())
210 q->show();
211 else
212 q->update();
213 }
214 if (!flashTimer)
215 flashTimer = q->startTimer(interval: 0);
216}
217
218void QScrollBarPrivate::activateControl(uint control, int threshold)
219{
220 QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction;
221 switch (control) {
222 case QStyle::SC_ScrollBarAddPage:
223 action = QAbstractSlider::SliderPageStepAdd;
224 break;
225 case QStyle::SC_ScrollBarSubPage:
226 action = QAbstractSlider::SliderPageStepSub;
227 break;
228 case QStyle::SC_ScrollBarAddLine:
229 action = QAbstractSlider::SliderSingleStepAdd;
230 break;
231 case QStyle::SC_ScrollBarSubLine:
232 action = QAbstractSlider::SliderSingleStepSub;
233 break;
234 case QStyle::SC_ScrollBarFirst:
235 action = QAbstractSlider::SliderToMinimum;
236 break;
237 case QStyle::SC_ScrollBarLast:
238 action = QAbstractSlider::SliderToMaximum;
239 break;
240 default:
241 break;
242 }
243
244 if (action) {
245 q_func()->setRepeatAction(action, thresholdTime: threshold);
246 q_func()->triggerAction(action);
247 }
248}
249
250void QScrollBarPrivate::stopRepeatAction()
251{
252 Q_Q(QScrollBar);
253 QStyle::SubControl tmp = pressedControl;
254 q->setRepeatAction(action: QAbstractSlider::SliderNoAction);
255 pressedControl = QStyle::SC_None;
256
257 if (tmp == QStyle::SC_ScrollBarSlider)
258 q->setSliderDown(false);
259
260 QStyleOptionSlider opt;
261 q->initStyleOption(option: &opt);
262 q->repaint(q->style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: tmp, widget: q));
263}
264
265/*!
266 Initialize \a option with the values from this QScrollBar. This method
267 is useful for subclasses when they need a QStyleOptionSlider, but don't want
268 to fill in all the information themselves.
269
270 \sa QStyleOption::initFrom()
271*/
272void QScrollBar::initStyleOption(QStyleOptionSlider *option) const
273{
274 if (!option)
275 return;
276
277 Q_D(const QScrollBar);
278 option->initFrom(w: this);
279 option->subControls = QStyle::SC_None;
280 option->activeSubControls = QStyle::SC_None;
281 option->orientation = d->orientation;
282 option->minimum = d->minimum;
283 option->maximum = d->maximum;
284 option->sliderPosition = d->position;
285 option->sliderValue = d->value;
286 option->singleStep = d->singleStep;
287 option->pageStep = d->pageStep;
288 option->upsideDown = d->invertedAppearance;
289 if (d->orientation == Qt::Horizontal)
290 option->state |= QStyle::State_Horizontal;
291 if ((d->flashed || !d->transient) && style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: option, widget: this))
292 option->state |= QStyle::State_On;
293}
294
295
296#define HORIZONTAL (d_func()->orientation == Qt::Horizontal)
297#define VERTICAL !HORIZONTAL
298
299/*!
300 Constructs a vertical scroll bar.
301
302 The \a parent argument is sent to the QWidget constructor.
303
304 The \l {QAbstractSlider::minimum} {minimum} defaults to 0, the
305 \l {QAbstractSlider::maximum} {maximum} to 99, with a
306 \l {QAbstractSlider::singleStep} {singleStep} size of 1 and a
307 \l {QAbstractSlider::pageStep} {pageStep} size of 10, and an
308 initial \l {QAbstractSlider::value} {value} of 0.
309*/
310QScrollBar::QScrollBar(QWidget *parent)
311 : QScrollBar(Qt::Vertical, parent)
312{
313}
314
315/*!
316 Constructs a scroll bar with the given \a orientation.
317
318 The \a parent argument is passed to the QWidget constructor.
319
320 The \l {QAbstractSlider::minimum} {minimum} defaults to 0, the
321 \l {QAbstractSlider::maximum} {maximum} to 99, with a
322 \l {QAbstractSlider::singleStep} {singleStep} size of 1 and a
323 \l {QAbstractSlider::pageStep} {pageStep} size of 10, and an
324 initial \l {QAbstractSlider::value} {value} of 0.
325*/
326QScrollBar::QScrollBar(Qt::Orientation orientation, QWidget *parent)
327 : QAbstractSlider(*new QScrollBarPrivate, parent)
328{
329 d_func()->orientation = orientation;
330 d_func()->init();
331}
332
333
334
335/*!
336 Destroys the scroll bar.
337*/
338QScrollBar::~QScrollBar()
339{
340}
341
342void QScrollBarPrivate::init()
343{
344 Q_Q(QScrollBar);
345 invertedControls = true;
346 pressedControl = hoverControl = QStyle::SC_None;
347 pointerOutsidePressedControl = false;
348 QStyleOption opt;
349 opt.initFrom(w: q);
350 transient = q->style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: q);
351 flashed = false;
352 flashTimer = 0;
353 q->setFocusPolicy(Qt::NoFocus);
354 QSizePolicy sp(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::Slider);
355 if (orientation == Qt::Vertical)
356 sp.transpose();
357 q->setSizePolicy(sp);
358 q->setAttribute(Qt::WA_WState_OwnSizePolicy, on: false);
359 q->setAttribute(Qt::WA_OpaquePaintEvent);
360}
361
362#ifndef QT_NO_CONTEXTMENU
363/*! \reimp */
364void QScrollBar::contextMenuEvent(QContextMenuEvent *event)
365{
366 if (!style()->styleHint(stylehint: QStyle::SH_ScrollBar_ContextMenu, opt: nullptr, widget: this)) {
367 QAbstractSlider::contextMenuEvent(event);
368 return ;
369 }
370
371#if QT_CONFIG(menu)
372 bool horiz = HORIZONTAL;
373 QPointer<QMenu> menu = new QMenu(this);
374 QAction *actScrollHere = menu->addAction(text: tr(s: "Scroll here"));
375 menu->addSeparator();
376 QAction *actScrollTop = menu->addAction(text: horiz ? tr(s: "Left edge") : tr(s: "Top"));
377 QAction *actScrollBottom = menu->addAction(text: horiz ? tr(s: "Right edge") : tr(s: "Bottom"));
378 menu->addSeparator();
379 QAction *actPageUp = menu->addAction(text: horiz ? tr(s: "Page left") : tr(s: "Page up"));
380 QAction *actPageDn = menu->addAction(text: horiz ? tr(s: "Page right") : tr(s: "Page down"));
381 menu->addSeparator();
382 QAction *actScrollUp = menu->addAction(text: horiz ? tr(s: "Scroll left") : tr(s: "Scroll up"));
383 QAction *actScrollDn = menu->addAction(text: horiz ? tr(s: "Scroll right") : tr(s: "Scroll down"));
384 QAction *actionSelected = menu->exec(pos: event->globalPos());
385 delete menu;
386 if (actionSelected == nullptr)
387 /* do nothing */ ;
388 else if (actionSelected == actScrollHere)
389 setValue(d_func()->pixelPosToRangeValue(pos: horiz ? event->pos().x() : event->pos().y()));
390 else if (actionSelected == actScrollTop)
391 triggerAction(action: QAbstractSlider::SliderToMinimum);
392 else if (actionSelected == actScrollBottom)
393 triggerAction(action: QAbstractSlider::SliderToMaximum);
394 else if (actionSelected == actPageUp)
395 triggerAction(action: QAbstractSlider::SliderPageStepSub);
396 else if (actionSelected == actPageDn)
397 triggerAction(action: QAbstractSlider::SliderPageStepAdd);
398 else if (actionSelected == actScrollUp)
399 triggerAction(action: QAbstractSlider::SliderSingleStepSub);
400 else if (actionSelected == actScrollDn)
401 triggerAction(action: QAbstractSlider::SliderSingleStepAdd);
402#endif // QT_CONFIG(menu)
403}
404#endif // QT_NO_CONTEXTMENU
405
406
407/*! \reimp */
408QSize QScrollBar::sizeHint() const
409{
410 ensurePolished();
411 QStyleOptionSlider opt;
412 initStyleOption(option: &opt);
413
414 int scrollBarExtent = style()->pixelMetric(metric: QStyle::PM_ScrollBarExtent, option: &opt, widget: this);
415 int scrollBarSliderMin = style()->pixelMetric(metric: QStyle::PM_ScrollBarSliderMin, option: &opt, widget: this);
416 QSize size;
417 if (opt.orientation == Qt::Horizontal)
418 size = QSize(scrollBarExtent * 2 + scrollBarSliderMin, scrollBarExtent);
419 else
420 size = QSize(scrollBarExtent, scrollBarExtent * 2 + scrollBarSliderMin);
421
422 return style()->sizeFromContents(ct: QStyle::CT_ScrollBar, opt: &opt, contentsSize: size, w: this);
423 }
424
425/*!\reimp */
426void QScrollBar::sliderChange(SliderChange change)
427{
428 QAbstractSlider::sliderChange(change);
429}
430
431/*!
432 \reimp
433*/
434bool QScrollBar::event(QEvent *event)
435{
436 Q_D(QScrollBar);
437 switch(event->type()) {
438 case QEvent::HoverEnter:
439 case QEvent::HoverLeave:
440 case QEvent::HoverMove:
441 if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
442 d_func()->updateHoverControl(pos: he->position().toPoint());
443 break;
444 case QEvent::StyleChange: {
445 QStyleOptionSlider opt;
446 initStyleOption(option: &opt);
447 d_func()->setTransient(style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: this));
448 break;
449 }
450 case QEvent::Timer:
451 if (static_cast<QTimerEvent *>(event)->timerId() == d->flashTimer) {
452 QStyleOptionSlider opt;
453 initStyleOption(option: &opt);
454 if (d->flashed && style()->styleHint(stylehint: QStyle::SH_ScrollBar_Transient, opt: &opt, widget: this)) {
455 d->flashed = false;
456 update();
457 }
458 killTimer(id: d->flashTimer);
459 d->flashTimer = 0;
460 }
461 break;
462 default:
463 break;
464 }
465 return QAbstractSlider::event(e: event);
466}
467
468/*!
469 \reimp
470*/
471#if QT_CONFIG(wheelevent)
472void QScrollBar::wheelEvent(QWheelEvent *event)
473{
474 event->ignore();
475 bool horizontal = qAbs(t: event->angleDelta().x()) > qAbs(t: event->angleDelta().y());
476 // The vertical wheel can be used to scroll a horizontal scrollbar, but only if
477 // there is no simultaneous horizontal wheel movement. This is to avoid chaotic
478 // scrolling on touchpads.
479 if (!horizontal && event->angleDelta().x() != 0 && orientation() == Qt::Horizontal)
480 return;
481 // scrollbar is a special case - in vertical mode it reaches minimum
482 // value in the upper position, however QSlider's minimum value is on
483 // the bottom. So we need to invert the value, but since the scrollbar is
484 // inverted by default, we need to invert the delta value only for the
485 // horizontal orientation.
486 int delta = horizontal ? -event->angleDelta().x() : event->angleDelta().y();
487 Q_D(QScrollBar);
488 if (d->scrollByDelta(orientation: horizontal ? Qt::Horizontal : Qt::Vertical, modifiers: event->modifiers(), delta))
489 event->accept();
490
491 if (event->phase() == Qt::ScrollBegin)
492 d->setTransient(false);
493 else if (event->phase() == Qt::ScrollEnd)
494 d->setTransient(true);
495}
496#endif
497
498/*!
499 \reimp
500*/
501void QScrollBar::paintEvent(QPaintEvent *)
502{
503 Q_D(QScrollBar);
504 QStylePainter p(this);
505 QStyleOptionSlider opt;
506 initStyleOption(option: &opt);
507 opt.subControls = QStyle::SC_All;
508 if (d->pressedControl) {
509 opt.activeSubControls = (QStyle::SubControl)d->pressedControl;
510 if (!d->pointerOutsidePressedControl)
511 opt.state |= QStyle::State_Sunken;
512 } else {
513 opt.activeSubControls = (QStyle::SubControl)d->hoverControl;
514 }
515 p.drawComplexControl(cc: QStyle::CC_ScrollBar, opt);
516}
517
518/*!
519 \reimp
520*/
521void QScrollBar::mousePressEvent(QMouseEvent *e)
522{
523 Q_D(QScrollBar);
524
525 if (d->repeatActionTimer.isActive())
526 d->stopRepeatAction();
527
528 bool midButtonAbsPos = style()->styleHint(stylehint: QStyle::SH_ScrollBar_MiddleClickAbsolutePosition,
529 opt: nullptr, widget: this);
530 QStyleOptionSlider opt;
531 initStyleOption(option: &opt);
532 opt.keyboardModifiers = e->modifiers();
533
534 if (d->maximum == d->minimum // no range
535 || (e->buttons() & (~e->button())) // another button was clicked before
536 || !(e->button() == Qt::LeftButton || (midButtonAbsPos && e->button() == Qt::MiddleButton)))
537 return;
538
539 d->pressedControl = style()->hitTestComplexControl(cc: QStyle::CC_ScrollBar, opt: &opt, pt: e->position().toPoint(), widget: this);
540 d->pointerOutsidePressedControl = false;
541
542 QRect sr = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt,
543 sc: QStyle::SC_ScrollBarSlider, widget: this);
544 QPoint click = e->position().toPoint();
545 QPoint pressValue = click - sr.center() + sr.topLeft();
546 d->pressValue = d->orientation == Qt::Horizontal ? d->pixelPosToRangeValue(pos: pressValue.x()) :
547 d->pixelPosToRangeValue(pos: pressValue.y());
548 if (d->pressedControl == QStyle::SC_ScrollBarSlider) {
549 d->clickOffset = HORIZONTAL ? (click.x()-sr.x()) : (click.y()-sr.y());
550 d->snapBackPosition = d->position;
551 }
552
553 if ((d->pressedControl == QStyle::SC_ScrollBarAddPage
554 || d->pressedControl == QStyle::SC_ScrollBarSubPage)
555 && ((midButtonAbsPos && e->button() == Qt::MiddleButton)
556 || (style()->styleHint(stylehint: QStyle::SH_ScrollBar_LeftClickAbsolutePosition, opt: &opt, widget: this)
557 && e->button() == Qt::LeftButton))) {
558 int sliderLength = HORIZONTAL ? sr.width() : sr.height();
559 setSliderPosition(d->pixelPosToRangeValue(pos: (HORIZONTAL ? e->position().toPoint().x()
560 : e->position().toPoint().y()) - sliderLength / 2));
561 d->pressedControl = QStyle::SC_ScrollBarSlider;
562 d->clickOffset = sliderLength / 2;
563 }
564 const int initialDelay = 500; // default threshold
565 QElapsedTimer time;
566 time.start();
567 d->activateControl(control: d->pressedControl, threshold: initialDelay);
568 repaint(style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: d->pressedControl, widget: this));
569 if (time.elapsed() >= initialDelay && d->repeatActionTimer.isActive()) {
570 // It took more than 500ms (the initial timer delay) to process
571 // the control activation and repaint(), we therefore need
572 // to restart the timer in case we have a pending mouse release event;
573 // otherwise we'll get a timer event right before the release event,
574 // causing the repeat action to be invoked twice on a single mouse click.
575 // 50ms is the default repeat time (see activateControl/setRepeatAction).
576 d->repeatActionTimer.start(msec: 50, obj: this);
577 }
578 if (d->pressedControl == QStyle::SC_ScrollBarSlider)
579 setSliderDown(true);
580}
581
582
583/*!
584 \reimp
585*/
586void QScrollBar::mouseReleaseEvent(QMouseEvent *e)
587{
588 Q_D(QScrollBar);
589 if (!d->pressedControl)
590 return;
591
592 if (e->buttons() & (~e->button())) // some other button is still pressed
593 return;
594
595 d->stopRepeatAction();
596}
597
598
599/*!
600 \reimp
601*/
602void QScrollBar::mouseMoveEvent(QMouseEvent *e)
603{
604 Q_D(QScrollBar);
605 if (!d->pressedControl)
606 return;
607
608 QStyleOptionSlider opt;
609 initStyleOption(option: &opt);
610 if (!(e->buttons() & Qt::LeftButton
611 || ((e->buttons() & Qt::MiddleButton)
612 && style()->styleHint(stylehint: QStyle::SH_ScrollBar_MiddleClickAbsolutePosition, opt: &opt, widget: this))))
613 return;
614
615 if (d->pressedControl == QStyle::SC_ScrollBarSlider) {
616 QPoint click = e->position().toPoint();
617 int newPosition = d->pixelPosToRangeValue(pos: (HORIZONTAL ? click.x() : click.y()) -d->clickOffset);
618 int m = style()->pixelMetric(metric: QStyle::PM_MaximumDragDistance, option: &opt, widget: this);
619 if (m >= 0) {
620 QRect r = rect();
621 r.adjust(dx1: -m, dy1: -m, dx2: m, dy2: m);
622 if (! r.contains(p: e->position().toPoint()))
623 newPosition = d->snapBackPosition;
624 }
625 setSliderPosition(newPosition);
626 } else if (!style()->styleHint(stylehint: QStyle::SH_ScrollBar_ScrollWhenPointerLeavesControl, opt: &opt, widget: this)) {
627
628 if (style()->styleHint(stylehint: QStyle::SH_ScrollBar_RollBetweenButtons, opt: &opt, widget: this)
629 && d->pressedControl & (QStyle::SC_ScrollBarAddLine | QStyle::SC_ScrollBarSubLine)) {
630 QStyle::SubControl newSc = style()->hitTestComplexControl(cc: QStyle::CC_ScrollBar, opt: &opt, pt: e->position().toPoint(), widget: this);
631 if (newSc == d->pressedControl && !d->pointerOutsidePressedControl)
632 return; // nothing to do
633 if (newSc & (QStyle::SC_ScrollBarAddLine | QStyle::SC_ScrollBarSubLine)) {
634 d->pointerOutsidePressedControl = false;
635 QRect scRect = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: newSc, widget: this);
636 scRect |= style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: d->pressedControl, widget: this);
637 d->pressedControl = newSc;
638 d->activateControl(control: d->pressedControl, threshold: 0);
639 update(scRect);
640 return;
641 }
642 }
643
644 // stop scrolling when the mouse pointer leaves a control
645 // similar to push buttons
646 QRect pr = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: d->pressedControl, widget: this);
647 if (pr.contains(p: e->position().toPoint()) == d->pointerOutsidePressedControl) {
648 if ((d->pointerOutsidePressedControl = !d->pointerOutsidePressedControl)) {
649 d->pointerOutsidePressedControl = true;
650 setRepeatAction(action: SliderNoAction);
651 repaint(pr);
652 } else {
653 d->activateControl(control: d->pressedControl);
654 }
655 }
656 }
657}
658
659
660int QScrollBarPrivate::pixelPosToRangeValue(int pos) const
661{
662 Q_Q(const QScrollBar);
663 QStyleOptionSlider opt;
664 q->initStyleOption(option: &opt);
665 QRect gr = q->style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt,
666 sc: QStyle::SC_ScrollBarGroove, widget: q);
667 QRect sr = q->style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt,
668 sc: QStyle::SC_ScrollBarSlider, widget: q);
669 int sliderMin, sliderMax, sliderLength;
670
671 if (orientation == Qt::Horizontal) {
672 sliderLength = sr.width();
673 sliderMin = gr.x();
674 sliderMax = gr.right() - sliderLength + 1;
675 if (q->layoutDirection() == Qt::RightToLeft)
676 opt.upsideDown = !opt.upsideDown;
677 } else {
678 sliderLength = sr.height();
679 sliderMin = gr.y();
680 sliderMax = gr.bottom() - sliderLength + 1;
681 }
682
683 return QStyle::sliderValueFromPosition(min: minimum, max: maximum, pos: pos - sliderMin,
684 space: sliderMax - sliderMin, upsideDown: opt.upsideDown);
685}
686
687/*! \reimp
688*/
689void QScrollBar::hideEvent(QHideEvent *)
690{
691 Q_D(QScrollBar);
692 if (d->pressedControl) {
693 d->pressedControl = QStyle::SC_None;
694 setRepeatAction(action: SliderNoAction);
695 }
696}
697
698/*! \internal
699 Returns the style option for scroll bar.
700*/
701Q_WIDGETS_EXPORT QStyleOptionSlider qt_qscrollbarStyleOption(QScrollBar *scrollbar)
702{
703 QStyleOptionSlider opt;
704 scrollbar->initStyleOption(option: &opt);
705 return opt;
706}
707
708QT_END_NAMESPACE
709
710#include "moc_qscrollbar.cpp"
711

source code of qtbase/src/widgets/widgets/qscrollbar.cpp