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