2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Staikos Computing Services Inc. <info@staikos.net>
4 * Copyright (C) 2007 Trolltech ASA
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "PlatformScrollBar.h"
32 #include "EventHandler.h"
33 #include "FrameView.h"
35 #include "GraphicsContext.h"
37 #include "PlatformMouseEvent.h"
39 #include <QApplication>
48 const double cInitialTimerDelay = 0.25;
49 const double cNormalTimerDelay = 0.05;
51 PlatformScrollbar::PlatformScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size)
52 : Scrollbar(client, orientation, size)
54 , m_pressedPart(QStyle::SC_None)
55 , m_hoveredPart(QStyle::SC_None)
56 , m_scrollTimer(this, &PlatformScrollbar::autoscrollTimerFired)
58 QStyle *s = QApplication::style();
60 m_opt.state = QStyle::State_Active | QStyle::State_Enabled;
61 m_opt.sliderValue = m_opt.sliderPosition = 0;
62 m_opt.upsideDown = false;
64 if (size != RegularScrollbar)
65 m_opt.state |= QStyle::State_Mini;
66 if (orientation == HorizontalScrollbar) {
67 m_opt.rect.setHeight(horizontalScrollbarHeight(size));
68 m_opt.orientation = Qt::Horizontal;
69 m_opt.state |= QStyle::State_Horizontal;
71 m_opt.rect.setWidth(verticalScrollbarWidth(size));
72 m_opt.orientation = Qt::Vertical;
73 m_opt.state &= ~QStyle::State_Horizontal;
77 PlatformScrollbar::~PlatformScrollbar()
82 void PlatformScrollbar::updateThumbPosition()
87 void PlatformScrollbar::updateThumbProportion()
92 int PlatformScrollbar::width() const
94 return m_opt.rect.width();
97 int PlatformScrollbar::height() const
99 return m_opt.rect.height();
102 void PlatformScrollbar::setRect(const IntRect& rect)
104 setFrameGeometry(rect);
107 IntRect PlatformScrollbar::frameGeometry() const
112 void PlatformScrollbar::setFrameGeometry(const IntRect& rect)
117 bool PlatformScrollbar::isEnabled() const
119 return m_opt.state & QStyle::State_Enabled;
122 void PlatformScrollbar::setEnabled(bool enabled)
124 if (enabled != isEnabled()) {
126 m_opt.state |= QStyle::State_Enabled;
128 m_opt.state &= ~QStyle::State_Enabled;
134 void PlatformScrollbar::paint(GraphicsContext* graphicsContext, const IntRect& damageRect)
136 if (controlSize() != RegularScrollbar) {
137 m_opt.state |= QStyle::State_Mini;
139 m_opt.state &= ~QStyle::State_Mini;
141 m_opt.orientation = (orientation() == VerticalScrollbar) ? Qt::Vertical : Qt::Horizontal;
142 QStyle *s = QApplication::style();
143 if (orientation() == HorizontalScrollbar) {
144 m_opt.rect.setHeight(horizontalScrollbarHeight(controlSize()));
145 m_opt.state |= QStyle::State_Horizontal;
147 m_opt.rect.setWidth(verticalScrollbarWidth(controlSize()));
148 m_opt.state &= ~QStyle::State_Horizontal;
151 if (graphicsContext->paintingDisabled() || !m_opt.rect.isValid())
154 // Don't paint anything if the scrollbar doesn't intersect the damage rect.
155 if (!m_opt.rect.intersects(damageRect))
158 QPainter *p = graphicsContext->platformContext();
159 m_opt.sliderValue = value();
160 m_opt.sliderPosition = value();
161 m_opt.pageStep = m_visibleSize;
162 m_opt.singleStep = m_lineStep;
164 m_opt.maximum = qMax(0, m_totalSize - m_visibleSize);
165 if (m_pressedPart != QStyle::SC_None) {
166 m_opt.activeSubControls = m_pressedPart;
168 m_opt.activeSubControls = m_hoveredPart;
171 const QPoint topLeft = m_opt.rect.topLeft();
172 p->translate(topLeft);
173 m_opt.rect.moveTo(QPoint(0, 0));
174 QApplication::style()->drawComplexControl(QStyle::CC_ScrollBar, &m_opt, p, 0);
175 m_opt.rect.moveTo(topLeft);
176 p->translate(-topLeft);
179 int PlatformScrollbar::thumbPosition() const
182 return (int)((float)m_currentPos * (trackLength() - thumbLength()) / (m_totalSize - m_visibleSize));
186 int PlatformScrollbar::thumbLength() const
188 IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0);
189 return m_orientation == HorizontalScrollbar ? thumb.width() : thumb.height();
192 int PlatformScrollbar::trackLength() const
194 IntRect track = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarGroove, 0);
195 return m_orientation == HorizontalScrollbar ? track.width() : track.height();
198 bool PlatformScrollbar::handleMouseMoveEvent(const PlatformMouseEvent& evt)
200 const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
201 //qDebug() << "PlatformScrollbar::handleMouseMoveEvent" << m_opt.rect << pos << evt.pos();
203 m_opt.state |= QStyle::State_MouseOver;
204 const QPoint ctlPt = m_opt.rect.topLeft();
205 m_opt.rect.moveTo(0, 0);
206 QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
207 m_opt.rect.moveTo(ctlPt);
209 if (sc == m_pressedPart) {
210 m_opt.state |= QStyle::State_Sunken;
212 m_opt.state &= ~QStyle::State_Sunken;
215 if (m_pressedPart == QStyle::SC_ScrollBarSlider) {
217 int thumbPos = thumbPosition();
218 int thumbLen = thumbLength();
219 int trackLen = trackLength();
220 int maxPos = trackLen - thumbLen;
222 if (m_orientation == HorizontalScrollbar)
223 delta = pos.x() - m_pressedPos;
225 delta = pos.y() - m_pressedPos;
228 // The mouse moved down/right.
229 delta = min(maxPos - thumbPos, delta);
231 // The mouse moved up/left.
232 delta = max(-thumbPos, delta);
235 setValue((int)((float)(thumbPos + delta) * (m_totalSize - m_visibleSize) / (trackLen - thumbLen)));
236 m_pressedPos += thumbPosition() - thumbPos;
242 if (m_pressedPart != QStyle::SC_None)
243 m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
245 if (sc != m_hoveredPart) {
246 if (m_pressedPart != QStyle::SC_None) {
247 if (sc == m_pressedPart) {
248 // The mouse is moving back over the pressed part. We
249 // need to start up the timer action again.
250 startTimerIfNeeded(cNormalTimerDelay);
252 } else if (m_hoveredPart == m_pressedPart) {
253 // The mouse is leaving the pressed part. Kill our timer
267 bool PlatformScrollbar::handleMouseOutEvent(const PlatformMouseEvent& evt)
269 m_opt.state &= ~QStyle::State_MouseOver;
270 m_opt.state &= ~QStyle::State_Sunken;
275 bool PlatformScrollbar::handleMousePressEvent(const PlatformMouseEvent& evt)
277 const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
278 //qDebug() << "PlatformScrollbar::handleMousePressEvent" << m_opt.rect << pos << evt.pos();
280 const QPoint ctlPt = m_opt.rect.topLeft();
281 m_opt.rect.moveTo(0, 0);
282 QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
283 m_opt.rect.moveTo(ctlPt);
285 case QStyle::SC_ScrollBarAddLine:
286 case QStyle::SC_ScrollBarSubLine:
287 case QStyle::SC_ScrollBarSlider:
288 m_opt.state |= QStyle::State_Sunken;
289 case QStyle::SC_ScrollBarAddPage:
290 case QStyle::SC_ScrollBarSubPage:
291 case QStyle::SC_ScrollBarGroove:
295 m_pressedPart = QStyle::SC_None;
298 m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
299 autoscrollPressedPart(cInitialTimerDelay);
304 bool PlatformScrollbar::handleMouseReleaseEvent(const PlatformMouseEvent&)
306 m_opt.state &= ~QStyle::State_Sunken;
307 m_pressedPart = QStyle::SC_None;
314 void PlatformScrollbar::startTimerIfNeeded(double delay)
316 // Don't do anything for the thumb.
317 if (m_pressedPart == QStyle::SC_ScrollBarSlider)
320 // Handle the track. We halt track scrolling once the thumb is level
322 if (m_pressedPart == QStyle::SC_ScrollBarGroove && thumbUnderMouse()) {
324 m_hoveredPart = QStyle::SC_ScrollBarSlider;
328 // We can't scroll if we've hit the beginning or end.
329 ScrollDirection dir = pressedPartScrollDirection();
330 if (dir == ScrollUp || dir == ScrollLeft) {
331 if (m_currentPos == 0)
334 if (m_currentPos == m_totalSize - m_visibleSize)
338 m_scrollTimer.startOneShot(delay);
341 void PlatformScrollbar::stopTimerIfNeeded()
343 if (m_scrollTimer.isActive())
344 m_scrollTimer.stop();
347 void PlatformScrollbar::autoscrollPressedPart(double delay)
349 // Don't do anything for the thumb or if nothing was pressed.
350 if (m_pressedPart == QStyle::SC_ScrollBarSlider || m_pressedPart == QStyle::SC_None)
354 if (m_pressedPart == QStyle::SC_ScrollBarGroove && thumbUnderMouse()) {
356 m_hoveredPart = QStyle::SC_ScrollBarSlider;
360 // Handle the arrows and track.
361 if (scroll(pressedPartScrollDirection(), pressedPartScrollGranularity()))
362 startTimerIfNeeded(delay);
365 void PlatformScrollbar::autoscrollTimerFired(Timer<PlatformScrollbar>*)
367 autoscrollPressedPart(cNormalTimerDelay);
370 ScrollDirection PlatformScrollbar::pressedPartScrollDirection()
372 if (m_orientation == HorizontalScrollbar) {
373 if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarSubPage)
377 if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarSubPage)
383 ScrollGranularity PlatformScrollbar::pressedPartScrollGranularity()
385 if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarAddLine)
390 bool PlatformScrollbar::thumbUnderMouse()
393 IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0);
394 thumb.move(-m_opt.rect.x(), -m_opt.rect.y());
395 int begin = (m_orientation == HorizontalScrollbar) ? thumb.x() : thumb.y();
396 int end = (m_orientation == HorizontalScrollbar) ? thumb.right() : thumb.bottom();
397 return (begin <= m_pressedPos && m_pressedPos < end);
400 int PlatformScrollbar::horizontalScrollbarHeight(ScrollbarControlSize controlSize)
402 QStyle *s = QApplication::style();
403 QStyleOptionSlider o;
404 o.orientation = Qt::Horizontal;
405 o.state |= QStyle::State_Horizontal;
406 if (controlSize != RegularScrollbar)
407 o.state |= QStyle::State_Mini;
408 return s->pixelMetric(QStyle::PM_ScrollBarExtent, &o, 0);
411 int PlatformScrollbar::verticalScrollbarWidth(ScrollbarControlSize controlSize)
413 QStyle *s = QApplication::style();
414 QStyleOptionSlider o;
415 o.orientation = Qt::Vertical;
416 o.state &= ~QStyle::State_Horizontal;
417 if (controlSize != RegularScrollbar)
418 o.state |= QStyle::State_Mini;
419 return s->pixelMetric(QStyle::PM_ScrollBarExtent, &o, 0);
422 IntRect PlatformScrollbar::windowClipRect() const
424 IntRect clipRect = m_opt.rect;
426 clipRect.intersect(m_client->windowClipRect());