/* * Copyright (C) 2007 Apple Inc. All rights reserved. * * 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" #ifdef USE_SAFARI_THEME #include "PlatformScrollBar.h" #include "EventHandler.h" #include "FrameView.h" #include "Frame.h" #include "GraphicsContext.h" #include "IntRect.h" #include "PlatformMouseEvent.h" #include #include // FIXME: There are repainting problems due to Aqua scroll bar buttons' visual overflow. using namespace std; namespace WebCore { using namespace SafariTheme; // FIXME: We should get these numbers from SafariTheme static int cHorizontalWidth[] = { 15, 11 }; static int cHorizontalHeight[] = { 15, 11 }; static int cVerticalWidth[] = { 15, 11 }; static int cVerticalHeight[] = { 15, 11 }; static int cRealButtonLength[] = { 28, 21 }; static int cButtonInset[] = { 14, 11 }; // cRealButtonLength - cButtonInset static int cButtonLength[] = { 14, 10 }; static int cThumbWidth[] = { 15, 11 }; static int cThumbHeight[] = { 15, 11 }; static int cThumbMinLength[] = { 26, 20 }; static paintThemePartPtr paintThemePart; static HMODULE themeDLL; const double cInitialTimerDelay = 0.25; const double cNormalTimerDelay = 0.05; PlatformScrollbar::PlatformScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size) : Scrollbar(client, orientation, size), m_hoveredPart(NoPart), m_pressedPart(NoPart), m_pressedPos(0), m_scrollTimer(this, &PlatformScrollbar::autoscrollTimerFired), m_overlapsResizer(false) { // Obtain the correct scrollbar sizes from the system. if (!cHorizontalWidth) { // FIXME: Get metics from SafariTheme } if (!themeDLL) themeDLL = ::LoadLibrary(SAFARITHEMEDLL); if (themeDLL) paintThemePart = (paintThemePartPtr)GetProcAddress(themeDLL, "paintThemePart"); if (orientation == VerticalScrollbar) setFrameGeometry(IntRect(0, 0, cVerticalWidth[controlSize()], cVerticalHeight[controlSize()])); else setFrameGeometry(IntRect(0, 0, cHorizontalWidth[controlSize()], cHorizontalHeight[controlSize()])); } PlatformScrollbar::~PlatformScrollbar() { stopTimerIfNeeded(); } void PlatformScrollbar::updateThumbPosition() { invalidateTrack(); } void PlatformScrollbar::updateThumbProportion() { invalidateTrack(); } static IntRect trackRepaintRect(const IntRect& trackRect, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) { IntRect paintRect(trackRect); if (orientation == HorizontalScrollbar) paintRect.inflateX(cButtonLength[controlSize]); else paintRect.inflateY(cButtonLength[controlSize]); return paintRect; } static IntRect buttonRepaintRect(const IntRect& buttonRect, ScrollbarOrientation orientation, ScrollbarControlSize controlSize, bool start) { IntRect paintRect(buttonRect); if (orientation == HorizontalScrollbar) { paintRect.setWidth(cRealButtonLength[controlSize]); if (!start) paintRect.setX(buttonRect.x() - (cRealButtonLength[controlSize] - buttonRect.width())); } else { paintRect.setHeight(cRealButtonLength[controlSize]); if (!start) paintRect.setY(buttonRect.y() - (cRealButtonLength[controlSize] - buttonRect.height())); } return paintRect; } void PlatformScrollbar::invalidateTrack() { IntRect rect = trackRepaintRect(trackRect(), m_orientation, controlSize()); rect.move(-x(), -y()); invalidateRect(rect); } void PlatformScrollbar::invalidatePart(ScrollbarPart part) { if (part == NoPart) return; IntRect result; switch (part) { case BackButtonPart: result = buttonRepaintRect(backButtonRect(), m_orientation, controlSize(), true); break; case ForwardButtonPart: result = buttonRepaintRect(forwardButtonRect(), m_orientation, controlSize(), false); break; default: { IntRect beforeThumbRect, thumbRect, afterThumbRect; splitTrack(trackRect(), beforeThumbRect, thumbRect, afterThumbRect); if (part == BackTrackPart) result = beforeThumbRect; else if (part == ForwardTrackPart) result = afterThumbRect; else result = thumbRect; } } result.move(-x(), -y()); invalidateRect(result); } int PlatformScrollbar::width() const { return Widget::width(); } int PlatformScrollbar::height() const { return Widget::height(); } void PlatformScrollbar::setRect(const IntRect& rect) { // Get our window resizer rect and see if we overlap. Adjust to avoid the overlap // if necessary. IntRect adjustedRect(rect); if (parent() && parent()->isFrameView()) { bool overlapsResizer = false; FrameView* view = static_cast(parent()); IntRect resizerRect = view->windowResizerRect(); resizerRect.setLocation(view->convertFromContainingWindow(resizerRect.location())); if (rect.intersects(resizerRect)) { if (orientation() == HorizontalScrollbar) { int overlap = rect.right() - resizerRect.x(); if (overlap > 0 && resizerRect.right() >= rect.right()) { adjustedRect.setWidth(rect.width() - overlap); overlapsResizer = true; } } else { int overlap = rect.bottom() - resizerRect.y(); if (overlap > 0 && resizerRect.bottom() >= rect.bottom()) { adjustedRect.setHeight(rect.height() - overlap); overlapsResizer = true; } } } if (overlapsResizer != m_overlapsResizer) { m_overlapsResizer = overlapsResizer; view->adjustOverlappingScrollbarCount(m_overlapsResizer ? 1 : -1); } } setFrameGeometry(adjustedRect); } void PlatformScrollbar::setParent(ScrollView* parentView) { if (!parentView && m_overlapsResizer && parent() && parent()->isFrameView()) static_cast(parent())->adjustOverlappingScrollbarCount(-1); Widget::setParent(parentView); } void PlatformScrollbar::setEnabled(bool enabled) { if (enabled != isEnabled()) { Widget::setEnabled(enabled); invalidate(); } } void PlatformScrollbar::paint(GraphicsContext* graphicsContext, const IntRect& damageRect) { if (graphicsContext->paintingDisabled()) return; // Don't paint anything if the scrollbar doesn't intersect the damage rect. if (!frameGeometry().intersects(damageRect)) return; IntRect track = trackRect(); paintTrack(graphicsContext, track, true, damageRect); if (isEnabled()) { paintButton(graphicsContext, backButtonRect(), true, damageRect); paintButton(graphicsContext, forwardButtonRect(), false, damageRect); } if (damageRect.intersects(track) && isEnabled()) { IntRect startTrackRect, thumbRect, endTrackRect; splitTrack(track, startTrackRect, thumbRect, endTrackRect); paintThumb(graphicsContext, thumbRect, damageRect); } } IntRect PlatformScrollbar::backButtonRect() const { // Our desired rect is essentially 17x17. // Our actual rect will shrink to half the available space when // we have < 34 pixels left. This allows the scrollbar // to scale down and function even at tiny sizes. if (m_orientation == HorizontalScrollbar) return IntRect(x(), y(), cButtonLength[controlSize()], cHorizontalHeight[controlSize()]); return IntRect(x(), y(), cVerticalWidth[controlSize()], cButtonLength[controlSize()]); } IntRect PlatformScrollbar::forwardButtonRect() const { // Our desired rect is essentially 17x17. // Our actual rect will shrink to half the available space when // we have < 34 pixels left. This allows the scrollbar // to scale down and function even at tiny sizes. if (m_orientation == HorizontalScrollbar) return IntRect(x() + width() - cButtonLength[controlSize()], y(), cButtonLength[controlSize()], cHorizontalHeight[controlSize()]); return IntRect(x(), y() + height() - cButtonLength[controlSize()], cVerticalWidth[controlSize()], cButtonLength[controlSize()]); } IntRect PlatformScrollbar::trackRect() const { if (m_orientation == HorizontalScrollbar) { if (width() < 2 * cHorizontalWidth[controlSize()]) return IntRect(); return IntRect(x() + cButtonLength[controlSize()], y(), width() - 2 * cButtonLength[controlSize()], cHorizontalHeight[controlSize()]); } if (height() < 2 * cVerticalHeight[controlSize()]) return IntRect(); return IntRect(x(), y() + cButtonLength[controlSize()], cVerticalWidth[controlSize()], height() - 2 * cButtonLength[controlSize()]); } IntRect PlatformScrollbar::thumbRect() const { IntRect beforeThumbRect, thumbRect, afterThumbRect; splitTrack(trackRect(), beforeThumbRect, thumbRect, afterThumbRect); return thumbRect; } void PlatformScrollbar::splitTrack(const IntRect& trackRect, IntRect& beforeThumbRect, IntRect& thumbRect, IntRect& afterThumbRect) const { // This function won't even get called unless we're big enough to have some combination of these three rects where at least // one of them is non-empty. int thumbPos = thumbPosition(); if (m_orientation == HorizontalScrollbar) { thumbRect = IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - cThumbHeight[controlSize()]) / 2, thumbLength(), cThumbHeight[controlSize()]); beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), thumbPos, trackRect.height()); afterThumbRect = IntRect(thumbRect.x() + thumbRect.width(), trackRect.y(), trackRect.right() - thumbRect.right(), trackRect.height()); } else { thumbRect = IntRect(trackRect.x() + (trackRect.width() - cThumbWidth[controlSize()]) / 2, trackRect.y() + thumbPos, cThumbWidth[controlSize()], thumbLength()); beforeThumbRect = IntRect(trackRect.x(), trackRect.y(), trackRect.width(), thumbPos); afterThumbRect = IntRect(trackRect.x(), thumbRect.y() + thumbRect.height(), trackRect.width(), trackRect.bottom() - thumbRect.bottom()); } } int PlatformScrollbar::thumbPosition() const { if (isEnabled()) return (float)m_currentPos * (trackLength() - thumbLength()) / (m_totalSize - m_visibleSize); return 0; } int PlatformScrollbar::thumbLength() const { if (!isEnabled()) return 0; float proportion = (float)(m_visibleSize) / m_totalSize; int trackLen = trackLength(); int length = proportion * trackLen; int minLength = cThumbMinLength[controlSize()]; length = max(length, minLength); if (length > trackLen) length = 0; // Once the thumb is below the track length, it just goes away (to make more room for the track). return length; } int PlatformScrollbar::trackLength() const { return (m_orientation == HorizontalScrollbar) ? trackRect().width() : trackRect().height(); } void PlatformScrollbar::paintButton(GraphicsContext* context, const IntRect& rect, bool start, const IntRect& damageRect) const { IntRect paintRect = buttonRepaintRect(rect, m_orientation, controlSize(), start); if (!damageRect.intersects(paintRect)) return; ThemePart part; ThemeControlState state = 0; if (m_orientation == HorizontalScrollbar) part = start ? ScrollLeftArrowPart : ScrollRightArrowPart; else part = start ? ScrollUpArrowPart : ScrollDownArrowPart; if (isEnabled()) state |= EnabledState; if ((m_pressedPart == BackButtonPart && start) || (m_pressedPart == ForwardButtonPart && !start)) state |= PressedState; paintThemePart(part, context->platformContext(), paintRect, controlSize() == SmallScrollbar ? NSSmallControlSize : NSRegularControlSize, state); } void PlatformScrollbar::paintTrack(GraphicsContext* context, const IntRect& rect, bool start, const IntRect& damageRect) const { IntRect paintRect = trackRepaintRect(rect, m_orientation, controlSize()); if (!damageRect.intersects(paintRect)) return; ThemePart part = m_orientation == HorizontalScrollbar ? HScrollTrackPart : VScrollTrackPart; ThemeControlState state = 0; if (isEnabled()) state |= EnabledState; paintThemePart(part, context->platformContext(), paintRect, controlSize() == SmallScrollbar ? NSSmallControlSize : NSRegularControlSize, state); } void PlatformScrollbar::paintThumb(GraphicsContext* context, const IntRect& rect, const IntRect& damageRect) const { if (!damageRect.intersects(rect)) return; ThemePart part = m_orientation == HorizontalScrollbar ? HScrollThumbPart : VScrollThumbPart; ThemeControlState state = 0; if (isEnabled()) state |= EnabledState; paintThemePart(part, context->platformContext(), rect, controlSize() == SmallScrollbar ? NSSmallControlSize : NSRegularControlSize, state); } ScrollbarPart PlatformScrollbar::hitTest(const PlatformMouseEvent& evt) { ScrollbarPart result = NoPart; if (!isEnabled()) return result; IntPoint mousePosition = convertFromContainingWindow(evt.pos()); mousePosition.move(x(), y()); if (backButtonRect().contains(mousePosition)) result = BackButtonPart; else if (forwardButtonRect().contains(mousePosition)) result = ForwardButtonPart; else { IntRect track = trackRect(); if (track.contains(mousePosition)) { IntRect beforeThumbRect, thumbRect, afterThumbRect; splitTrack(track, beforeThumbRect, thumbRect, afterThumbRect); if (beforeThumbRect.contains(mousePosition)) result = BackTrackPart; else if (thumbRect.contains(mousePosition)) result = ThumbPart; else result = ForwardTrackPart; } } return result; } bool PlatformScrollbar::handleMouseMoveEvent(const PlatformMouseEvent& evt) { if (m_pressedPart == ThumbPart) { // Drag the thumb. int thumbPos = thumbPosition(); int thumbLen = thumbLength(); int trackLen = trackLength(); int maxPos = trackLen - thumbLen; int delta = 0; if (m_orientation == HorizontalScrollbar) delta = convertFromContainingWindow(evt.pos()).x() - m_pressedPos; else delta = convertFromContainingWindow(evt.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 != NoPart) m_pressedPos = (m_orientation == HorizontalScrollbar ? convertFromContainingWindow(evt.pos()).x() : convertFromContainingWindow(evt.pos()).y()); ScrollbarPart part = hitTest(evt); if (part != m_hoveredPart) { if (m_pressedPart != NoPart) { if (part == m_pressedPart) { // The mouse is moving back over the pressed part. We // need to start up the timer action again. startTimerIfNeeded(cNormalTimerDelay); invalidatePart(m_pressedPart); } else if (m_hoveredPart == m_pressedPart) { // The mouse is leaving the pressed part. Kill our timer // if needed. stopTimerIfNeeded(); invalidatePart(m_pressedPart); } } else { invalidatePart(part); invalidatePart(m_hoveredPart); } m_hoveredPart = part; } return true; } bool PlatformScrollbar::handleMouseOutEvent(const PlatformMouseEvent& evt) { invalidatePart(m_hoveredPart); m_hoveredPart = NoPart; return true; } bool PlatformScrollbar::handleMousePressEvent(const PlatformMouseEvent& evt) { m_pressedPart = hitTest(evt); m_pressedPos = (m_orientation == HorizontalScrollbar ? convertFromContainingWindow(evt.pos()).x() : convertFromContainingWindow(evt.pos()).y()); invalidatePart(m_pressedPart); autoscrollPressedPart(cInitialTimerDelay); return true; } bool PlatformScrollbar::handleMouseReleaseEvent(const PlatformMouseEvent& evt) { invalidatePart(m_pressedPart); m_pressedPart = NoPart; m_pressedPos = 0; stopTimerIfNeeded(); if (parent() && parent()->isFrameView()) static_cast(parent())->frame()->eventHandler()->setMousePressed(false); return true; } void PlatformScrollbar::startTimerIfNeeded(double delay) { // Don't do anything for the thumb. if (m_pressedPart == ThumbPart) return; // Handle the track. We halt track scrolling once the thumb is level // with us. if ((m_pressedPart == BackTrackPart || m_pressedPart == ForwardTrackPart) && thumbUnderMouse()) { invalidatePart(m_pressedPart); m_hoveredPart = ThumbPart; 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 == ThumbPart || m_pressedPart == NoPart) return; // Handle the track. if ((m_pressedPart == BackTrackPart || m_pressedPart == ForwardTrackPart) && thumbUnderMouse()) { invalidatePart(m_pressedPart); m_hoveredPart = ThumbPart; 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 == BackButtonPart || m_pressedPart == BackTrackPart) return ScrollLeft; return ScrollRight; } else { if (m_pressedPart == BackButtonPart || m_pressedPart == BackTrackPart) return ScrollUp; return ScrollDown; } } ScrollGranularity PlatformScrollbar::pressedPartScrollGranularity() { if (m_pressedPart == BackButtonPart || m_pressedPart == ForwardButtonPart) return ScrollByLine; return ScrollByPage; } bool PlatformScrollbar::thumbUnderMouse() { // Construct a rect. IntRect thumb = thumbRect(); thumb.move(-x(), -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) { return cHorizontalWidth[controlSize]; } int PlatformScrollbar::verticalScrollbarWidth(ScrollbarControlSize controlSize) { return cVerticalHeight[controlSize]; } IntRect PlatformScrollbar::windowClipRect() const { IntRect clipRect(0, 0, width(), height()); clipRect = convertToContainingWindow(clipRect); if (m_client) clipRect.intersect(m_client->windowClipRect()); return clipRect; } void PlatformScrollbar::paintGripper(HDC hdc, const IntRect& rect) const { } IntRect PlatformScrollbar::gripperRect(const IntRect& thumbRect) const { return IntRect(); } void PlatformScrollbar::themeChanged() { } } #endif // defined(USE_SAFARI_THEME)