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