/* * Copyright (C) 2007 Apple Inc. All rights reserved. * Copyright (C) 2007 Staikos Computing Services Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "PlatformScrollBar.h" #include "EventHandler.h" #include "FrameView.h" #include "Frame.h" #include "GraphicsContext.h" #include "IntRect.h" #include "PlatformMouseEvent.h" #include #include #include #include using namespace std; namespace WebCore { const double cInitialTimerDelay = 0.25; const double cNormalTimerDelay = 0.05; PlatformScrollbar::PlatformScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size) : Scrollbar(client, orientation, size), m_pressedPos(0), m_pressedPart(QStyle::SC_None), m_hoveredPart(QStyle::SC_None), m_scrollTimer(this, &PlatformScrollbar::autoscrollTimerFired) { QStyle *s = QApplication::style(); m_opt.state = QStyle::State_Active | QStyle::State_Enabled; m_opt.sliderValue = m_opt.sliderPosition = 0; m_opt.upsideDown = false; setEnabled(true); if (size != RegularScrollbar) m_opt.state |= QStyle::State_Mini; if (orientation == HorizontalScrollbar) { m_opt.rect.setHeight(horizontalScrollbarHeight(size)); m_opt.orientation = Qt::Horizontal; m_opt.state |= QStyle::State_Horizontal; } else { m_opt.rect.setWidth(verticalScrollbarWidth(size)); m_opt.orientation = Qt::Vertical; m_opt.state &= ~QStyle::State_Horizontal; } } PlatformScrollbar::~PlatformScrollbar() { stopTimerIfNeeded(); } void PlatformScrollbar::updateThumbPosition() { invalidate(); } void PlatformScrollbar::updateThumbProportion() { invalidate(); } int PlatformScrollbar::width() const { return m_opt.rect.width(); } int PlatformScrollbar::height() const { return m_opt.rect.height(); } void PlatformScrollbar::setRect(const IntRect& rect) { setFrameGeometry(rect); } IntRect PlatformScrollbar::frameGeometry() const { return m_opt.rect; } void PlatformScrollbar::setFrameGeometry(const IntRect& rect) { m_opt.rect = rect; } bool PlatformScrollbar::isEnabled() const { return m_opt.state & QStyle::State_Enabled; } void PlatformScrollbar::setEnabled(bool enabled) { if (enabled != isEnabled()) { if (enabled) { m_opt.state |= QStyle::State_Enabled; } else { m_opt.state &= ~QStyle::State_Enabled; } invalidate(); } } void PlatformScrollbar::paint(GraphicsContext* graphicsContext, const IntRect& damageRect) { if (controlSize() != RegularScrollbar) { m_opt.state |= QStyle::State_Mini; } else { m_opt.state &= ~QStyle::State_Mini; } m_opt.orientation = (orientation() == VerticalScrollbar) ? Qt::Vertical : Qt::Horizontal; QStyle *s = QApplication::style(); if (orientation() == HorizontalScrollbar) { m_opt.rect.setHeight(horizontalScrollbarHeight(controlSize())); m_opt.state |= QStyle::State_Horizontal; } else { m_opt.rect.setWidth(verticalScrollbarWidth(controlSize())); m_opt.state &= ~QStyle::State_Horizontal; } if (graphicsContext->paintingDisabled() || !m_opt.rect.isValid()) return; // Don't paint anything if the scrollbar doesn't intersect the damage rect. if (!m_opt.rect.intersects(damageRect)) return; QPainter *p = graphicsContext->platformContext(); m_opt.sliderValue = value(); m_opt.sliderPosition = value(); m_opt.pageStep = m_visibleSize; m_opt.singleStep = m_lineStep; m_opt.minimum = 0; m_opt.maximum = qMax(0, m_totalSize - m_visibleSize); if (m_pressedPart != QStyle::SC_None) { m_opt.activeSubControls = m_pressedPart; } else { m_opt.activeSubControls = m_hoveredPart; } const QPoint topLeft = m_opt.rect.topLeft(); p->translate(topLeft); m_opt.rect.moveTo(QPoint(0, 0)); QApplication::style()->drawComplexControl(QStyle::CC_ScrollBar, &m_opt, p, 0); m_opt.rect.moveTo(topLeft); p->translate(-topLeft); } int PlatformScrollbar::thumbPosition() const { if (isEnabled()) return (float)m_currentPos * (trackLength() - thumbLength()) / (m_totalSize - m_visibleSize); return 0; } int PlatformScrollbar::thumbLength() const { IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0); return m_orientation == HorizontalScrollbar ? thumb.width() : thumb.height(); } int PlatformScrollbar::trackLength() const { IntRect track = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarGroove, 0); return m_orientation == HorizontalScrollbar ? track.width() : track.height(); } bool PlatformScrollbar::handleMouseMoveEvent(const PlatformMouseEvent& evt) { const QPoint pos = parent()->convertFromContainingWindow(evt.pos()); //qDebug() << "PlatformScrollbar::handleMouseMoveEvent" << m_opt.rect << pos << endl; m_opt.state |= QStyle::State_MouseOver; const QPoint ctlPt = m_opt.rect.topLeft(); m_opt.rect.moveTo(0, 0); QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0); m_opt.rect.moveTo(ctlPt); if (sc == m_pressedPart) { m_opt.state |= QStyle::State_Sunken; } else { m_opt.state &= ~QStyle::State_Sunken; } if (m_pressedPart == QStyle::SC_ScrollBarSlider) { // Drag the thumb. int thumbPos = thumbPosition(); int thumbLen = thumbLength(); int trackLen = trackLength(); int maxPos = trackLen - thumbLen; int delta = 0; if (m_orientation == HorizontalScrollbar) delta = pos.x() - m_pressedPos; else delta = pos.y() - m_pressedPos; if (delta > 0) // The mouse moved down/right. delta = min(maxPos - thumbPos, delta); else if (delta < 0) // The mouse moved up/left. delta = max(-thumbPos, delta); if (delta != 0) { setValue((float)(thumbPos + delta) * (m_totalSize - m_visibleSize) / (trackLen - thumbLen)); m_pressedPos += thumbPosition() - thumbPos; } return true; } if (m_pressedPart != QStyle::SC_None) m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y(); if (sc != m_hoveredPart) { if (m_pressedPart != QStyle::SC_None) { if (sc == m_pressedPart) { // The mouse is moving back over the pressed part. We // need to start up the timer action again. startTimerIfNeeded(cNormalTimerDelay); invalidate(); } else if (m_hoveredPart == m_pressedPart) { // The mouse is leaving the pressed part. Kill our timer // if needed. stopTimerIfNeeded(); invalidate(); } } else { invalidate(); } m_hoveredPart = sc; } return true; } bool PlatformScrollbar::handleMouseOutEvent(const PlatformMouseEvent& evt) { m_opt.state &= ~QStyle::State_MouseOver; m_opt.state &= ~QStyle::State_Sunken; invalidate(); return true; } bool PlatformScrollbar::handleMousePressEvent(const PlatformMouseEvent& evt) { const QPoint pos = parent()->convertFromContainingWindow(evt.pos()); //qDebug() << "PlatformScrollbar::handleMousePressEvent" << m_opt.rect << pos << endl; const QPoint ctlPt = m_opt.rect.topLeft(); m_opt.rect.moveTo(0, 0); QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0); m_opt.rect.moveTo(ctlPt); switch (sc) { case QStyle::SC_ScrollBarAddLine: case QStyle::SC_ScrollBarSubLine: case QStyle::SC_ScrollBarSlider: m_opt.state |= QStyle::State_Sunken; case QStyle::SC_ScrollBarAddPage: case QStyle::SC_ScrollBarSubPage: case QStyle::SC_ScrollBarGroove: m_pressedPart = sc; break; default: m_pressedPart = QStyle::SC_None; return false; } m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y(); autoscrollPressedPart(cInitialTimerDelay); invalidate(); return true; } bool PlatformScrollbar::handleMouseReleaseEvent(const PlatformMouseEvent& evt) { const QPoint pos = parent()->convertFromContainingWindow(evt.pos()); //qDebug() << "PlatformScrollbar::handleMouseReleaseEvent" << m_opt.rect << pos << endl; m_opt.state &= ~QStyle::State_Sunken; m_pressedPart = QStyle::SC_None; m_pressedPos = 0; stopTimerIfNeeded(); invalidate(); return true; } void PlatformScrollbar::startTimerIfNeeded(double delay) { // Don't do anything for the thumb. if (m_pressedPart == QStyle::SC_ScrollBarSlider) return; // Handle the track. We halt track scrolling once the thumb is level // with us. if (m_pressedPart == QStyle::SC_ScrollBarGroove && thumbUnderMouse()) { invalidate(); m_hoveredPart = QStyle::SC_ScrollBarSlider; return; } // We can't scroll if we've hit the beginning or end. ScrollDirection dir = pressedPartScrollDirection(); if (dir == ScrollUp || dir == ScrollLeft) { if (m_currentPos == 0) return; } else { if (m_currentPos == m_totalSize - m_visibleSize) return; } m_scrollTimer.startOneShot(delay); } void PlatformScrollbar::stopTimerIfNeeded() { if (m_scrollTimer.isActive()) m_scrollTimer.stop(); } void PlatformScrollbar::autoscrollPressedPart(double delay) { // Don't do anything for the thumb or if nothing was pressed. if (m_pressedPart == QStyle::SC_ScrollBarSlider || m_pressedPart == QStyle::SC_None) return; // Handle the track. if (m_pressedPart == QStyle::SC_ScrollBarGroove && thumbUnderMouse()) { invalidate(); m_hoveredPart = QStyle::SC_ScrollBarSlider; return; } // Handle the arrows and track. if (scroll(pressedPartScrollDirection(), pressedPartScrollGranularity())) startTimerIfNeeded(delay); } void PlatformScrollbar::autoscrollTimerFired(Timer*) { autoscrollPressedPart(cNormalTimerDelay); } ScrollDirection PlatformScrollbar::pressedPartScrollDirection() { if (m_orientation == HorizontalScrollbar) { if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarSubPage) return ScrollLeft; return ScrollRight; } else { if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarSubPage) return ScrollUp; return ScrollDown; } } ScrollGranularity PlatformScrollbar::pressedPartScrollGranularity() { if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarAddLine) return ScrollByLine; return ScrollByPage; } bool PlatformScrollbar::thumbUnderMouse() { // Construct a rect. IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0); thumb.move(-m_opt.rect.x(), -m_opt.rect.y()); int begin = (m_orientation == HorizontalScrollbar) ? thumb.x() : thumb.y(); int end = (m_orientation == HorizontalScrollbar) ? thumb.right() : thumb.bottom(); return (begin <= m_pressedPos && m_pressedPos < end); } int PlatformScrollbar::horizontalScrollbarHeight(ScrollbarControlSize controlSize) { QStyle *s = QApplication::style(); QStyleOptionSlider o; o.orientation = Qt::Horizontal; o.state |= QStyle::State_Horizontal; if (controlSize != RegularScrollbar) o.state |= QStyle::State_Mini; return s->pixelMetric(QStyle::PM_ScrollBarExtent, &o, 0); } int PlatformScrollbar::verticalScrollbarWidth(ScrollbarControlSize controlSize) { QStyle *s = QApplication::style(); QStyleOptionSlider o; o.orientation = Qt::Vertical; o.state &= ~QStyle::State_Horizontal; if (controlSize != RegularScrollbar) o.state |= QStyle::State_Mini; return s->pixelMetric(QStyle::PM_ScrollBarExtent, &o, 0); } IntRect PlatformScrollbar::windowClipRect() const { IntRect clipRect = m_opt.rect; if (m_client) clipRect.intersect(m_client->windowClipRect()); return clipRect; } } // vim: ts=4 sw=4 et