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