2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Staikos Computing Services Inc. <info@staikos.net>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "PlatformScrollBar.h"
31 #include "EventHandler.h"
32 #include "FrameView.h"
34 #include "GraphicsContext.h"
36 #include "PlatformMouseEvent.h"
38 #include <QApplication>
47 const double cInitialTimerDelay = 0.25;
48 const double cNormalTimerDelay = 0.05;
50 PlatformScrollbar::PlatformScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size)
51 : Scrollbar(client, orientation, size), m_pressedPos(0),
52 m_pressedPart(QStyle::SC_None),
53 m_hoveredPart(QStyle::SC_None),
54 m_scrollTimer(this, &PlatformScrollbar::autoscrollTimerFired)
56 QStyle *s = QApplication::style();
58 m_opt.state = QStyle::State_Active | QStyle::State_Enabled;
59 m_opt.sliderValue = m_opt.sliderPosition = 0;
60 m_opt.upsideDown = false;
62 if (size != RegularScrollbar)
63 m_opt.state |= QStyle::State_Mini;
64 if (orientation == HorizontalScrollbar) {
65 m_opt.rect.setHeight(horizontalScrollbarHeight(size));
66 m_opt.orientation = Qt::Horizontal;
67 m_opt.state |= QStyle::State_Horizontal;
69 m_opt.rect.setWidth(verticalScrollbarWidth(size));
70 m_opt.orientation = Qt::Vertical;
71 m_opt.state &= ~QStyle::State_Horizontal;
75 PlatformScrollbar::~PlatformScrollbar()
80 void PlatformScrollbar::updateThumbPosition()
85 void PlatformScrollbar::updateThumbProportion()
90 int PlatformScrollbar::width() const
92 return m_opt.rect.width();
95 int PlatformScrollbar::height() const
97 return m_opt.rect.height();
100 void PlatformScrollbar::setRect(const IntRect& rect)
102 setFrameGeometry(rect);
105 IntRect PlatformScrollbar::frameGeometry() const
110 void PlatformScrollbar::setFrameGeometry(const IntRect& rect)
115 bool PlatformScrollbar::isEnabled() const
117 return m_opt.state & QStyle::State_Enabled;
120 void PlatformScrollbar::setEnabled(bool enabled)
122 if (enabled != isEnabled()) {
124 m_opt.state |= QStyle::State_Enabled;
126 m_opt.state &= ~QStyle::State_Enabled;
132 void PlatformScrollbar::paint(GraphicsContext* graphicsContext, const IntRect& damageRect)
134 if (controlSize() != RegularScrollbar) {
135 m_opt.state |= QStyle::State_Mini;
137 m_opt.state &= ~QStyle::State_Mini;
139 m_opt.orientation = (orientation() == VerticalScrollbar) ? Qt::Vertical : Qt::Horizontal;
140 QStyle *s = QApplication::style();
141 if (orientation() == HorizontalScrollbar) {
142 m_opt.rect.setHeight(horizontalScrollbarHeight(controlSize()));
143 m_opt.state |= QStyle::State_Horizontal;
145 m_opt.rect.setWidth(verticalScrollbarWidth(controlSize()));
146 m_opt.state &= ~QStyle::State_Horizontal;
149 if (graphicsContext->paintingDisabled() || !m_opt.rect.isValid())
152 // Don't paint anything if the scrollbar doesn't intersect the damage rect.
153 if (!m_opt.rect.intersects(damageRect))
156 QPainter *p = graphicsContext->platformContext();
157 m_opt.sliderValue = value();
158 m_opt.sliderPosition = value();
159 m_opt.pageStep = m_visibleSize;
160 m_opt.singleStep = m_lineStep;
162 m_opt.maximum = qMax(0, m_totalSize - m_visibleSize);
163 if (m_pressedPart != QStyle::SC_None) {
164 m_opt.activeSubControls = m_pressedPart;
166 m_opt.activeSubControls = m_hoveredPart;
169 const QPoint topLeft = m_opt.rect.topLeft();
170 p->translate(topLeft);
171 m_opt.rect.moveTo(QPoint(0, 0));
172 QApplication::style()->drawComplexControl(QStyle::CC_ScrollBar, &m_opt, p, 0);
173 m_opt.rect.moveTo(topLeft);
174 p->translate(-topLeft);
177 int PlatformScrollbar::thumbPosition() const
180 return (int)((float)m_currentPos * (trackLength() - thumbLength()) / (m_totalSize - m_visibleSize));
184 int PlatformScrollbar::thumbLength() const
186 IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0);
187 return m_orientation == HorizontalScrollbar ? thumb.width() : thumb.height();
190 int PlatformScrollbar::trackLength() const
192 IntRect track = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarGroove, 0);
193 return m_orientation == HorizontalScrollbar ? track.width() : track.height();
196 bool PlatformScrollbar::handleMouseMoveEvent(const PlatformMouseEvent& evt)
198 const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
199 //qDebug() << "PlatformScrollbar::handleMouseMoveEvent" << m_opt.rect << pos << endl;
201 m_opt.state |= QStyle::State_MouseOver;
202 const QPoint ctlPt = m_opt.rect.topLeft();
203 m_opt.rect.moveTo(0, 0);
204 QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
205 m_opt.rect.moveTo(ctlPt);
207 if (sc == m_pressedPart) {
208 m_opt.state |= QStyle::State_Sunken;
210 m_opt.state &= ~QStyle::State_Sunken;
213 if (m_pressedPart == QStyle::SC_ScrollBarSlider) {
215 int thumbPos = thumbPosition();
216 int thumbLen = thumbLength();
217 int trackLen = trackLength();
218 int maxPos = trackLen - thumbLen;
220 if (m_orientation == HorizontalScrollbar)
221 delta = pos.x() - m_pressedPos;
223 delta = pos.y() - m_pressedPos;
226 // The mouse moved down/right.
227 delta = min(maxPos - thumbPos, delta);
229 // The mouse moved up/left.
230 delta = max(-thumbPos, delta);
233 setValue((int)((float)(thumbPos + delta) * (m_totalSize - m_visibleSize) / (trackLen - thumbLen)));
234 m_pressedPos += thumbPosition() - thumbPos;
240 if (m_pressedPart != QStyle::SC_None)
241 m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
243 if (sc != m_hoveredPart) {
244 if (m_pressedPart != QStyle::SC_None) {
245 if (sc == m_pressedPart) {
246 // The mouse is moving back over the pressed part. We
247 // need to start up the timer action again.
248 startTimerIfNeeded(cNormalTimerDelay);
250 } else if (m_hoveredPart == m_pressedPart) {
251 // The mouse is leaving the pressed part. Kill our timer
265 bool PlatformScrollbar::handleMouseOutEvent(const PlatformMouseEvent& evt)
267 m_opt.state &= ~QStyle::State_MouseOver;
268 m_opt.state &= ~QStyle::State_Sunken;
273 bool PlatformScrollbar::handleMousePressEvent(const PlatformMouseEvent& evt)
275 const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
276 //qDebug() << "PlatformScrollbar::handleMousePressEvent" << m_opt.rect << pos << endl;
278 const QPoint ctlPt = m_opt.rect.topLeft();
279 m_opt.rect.moveTo(0, 0);
280 QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
281 m_opt.rect.moveTo(ctlPt);
283 case QStyle::SC_ScrollBarAddLine:
284 case QStyle::SC_ScrollBarSubLine:
285 case QStyle::SC_ScrollBarSlider:
286 m_opt.state |= QStyle::State_Sunken;
287 case QStyle::SC_ScrollBarAddPage:
288 case QStyle::SC_ScrollBarSubPage:
289 case QStyle::SC_ScrollBarGroove:
293 m_pressedPart = QStyle::SC_None;
296 m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
297 autoscrollPressedPart(cInitialTimerDelay);
302 bool PlatformScrollbar::handleMouseReleaseEvent(const PlatformMouseEvent& evt)
304 const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
305 //qDebug() << "PlatformScrollbar::handleMouseReleaseEvent" << m_opt.rect << pos << endl;
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());