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