/* * Copyright (C) 2006, 2007, 2008 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" #include "platform/scroll/ScrollView.h" #include "platform/graphics/GraphicsContextStateSaver.h" #include "platform/graphics/GraphicsLayer.h" #include "platform/HostWindow.h" #include "platform/scroll/ScrollbarTheme.h" #include "wtf/StdLibExtras.h" using namespace std; namespace WebCore { ScrollView::ScrollView() : m_horizontalScrollbarMode(ScrollbarAuto) , m_verticalScrollbarMode(ScrollbarAuto) , m_horizontalScrollbarLock(false) , m_verticalScrollbarLock(false) , m_canBlitOnScroll(true) , m_scrollbarsAvoidingResizer(0) , m_scrollbarsSuppressed(false) , m_inUpdateScrollbars(false) , m_updateScrollbarsPass(0) , m_drawPanScrollIcon(false) , m_paintsEntireContents(false) , m_clipsRepaints(true) { } ScrollView::~ScrollView() { } void ScrollView::addChild(PassRefPtr prpChild) { Widget* child = prpChild.get(); ASSERT(child != this && !child->parent()); child->setParent(this); m_children.add(prpChild); } void ScrollView::removeChild(Widget* child) { ASSERT(child->parent() == this); child->setParent(0); m_children.remove(child); } void ScrollView::setHasHorizontalScrollbar(bool hasBar) { if (hasBar && !m_horizontalScrollbar) { m_horizontalScrollbar = createScrollbar(HorizontalScrollbar); addChild(m_horizontalScrollbar.get()); didAddScrollbar(m_horizontalScrollbar.get(), HorizontalScrollbar); m_horizontalScrollbar->styleChanged(); } else if (!hasBar && m_horizontalScrollbar) { willRemoveScrollbar(m_horizontalScrollbar.get(), HorizontalScrollbar); removeChild(m_horizontalScrollbar.get()); m_horizontalScrollbar = 0; } } void ScrollView::setHasVerticalScrollbar(bool hasBar) { if (hasBar && !m_verticalScrollbar) { m_verticalScrollbar = createScrollbar(VerticalScrollbar); addChild(m_verticalScrollbar.get()); didAddScrollbar(m_verticalScrollbar.get(), VerticalScrollbar); m_verticalScrollbar->styleChanged(); } else if (!hasBar && m_verticalScrollbar) { willRemoveScrollbar(m_verticalScrollbar.get(), VerticalScrollbar); removeChild(m_verticalScrollbar.get()); m_verticalScrollbar = 0; } } PassRefPtr ScrollView::createScrollbar(ScrollbarOrientation orientation) { return Scrollbar::create(this, orientation, RegularScrollbar); } void ScrollView::setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode, bool horizontalLock, bool verticalLock) { bool needsUpdate = false; if (horizontalMode != horizontalScrollbarMode() && !m_horizontalScrollbarLock) { m_horizontalScrollbarMode = horizontalMode; needsUpdate = true; } if (verticalMode != verticalScrollbarMode() && !m_verticalScrollbarLock) { m_verticalScrollbarMode = verticalMode; needsUpdate = true; } if (horizontalLock) setHorizontalScrollbarLock(); if (verticalLock) setVerticalScrollbarLock(); if (!needsUpdate) return; updateScrollbars(scrollOffset()); if (!layerForScrolling()) return; blink::WebLayer* layer = layerForScrolling()->platformLayer(); if (!layer) return; layer->setUserScrollable(userInputScrollable(HorizontalScrollbar), userInputScrollable(VerticalScrollbar)); } void ScrollView::scrollbarModes(ScrollbarMode& horizontalMode, ScrollbarMode& verticalMode) const { horizontalMode = m_horizontalScrollbarMode; verticalMode = m_verticalScrollbarMode; } void ScrollView::setCanHaveScrollbars(bool canScroll) { ScrollbarMode newHorizontalMode; ScrollbarMode newVerticalMode; scrollbarModes(newHorizontalMode, newVerticalMode); if (canScroll && newVerticalMode == ScrollbarAlwaysOff) newVerticalMode = ScrollbarAuto; else if (!canScroll) newVerticalMode = ScrollbarAlwaysOff; if (canScroll && newHorizontalMode == ScrollbarAlwaysOff) newHorizontalMode = ScrollbarAuto; else if (!canScroll) newHorizontalMode = ScrollbarAlwaysOff; setScrollbarModes(newHorizontalMode, newVerticalMode); } void ScrollView::setCanBlitOnScroll(bool b) { m_canBlitOnScroll = b; } bool ScrollView::canBlitOnScroll() const { return m_canBlitOnScroll; } void ScrollView::setPaintsEntireContents(bool paintsEntireContents) { m_paintsEntireContents = paintsEntireContents; } void ScrollView::setClipsRepaints(bool clipsRepaints) { m_clipsRepaints = clipsRepaints; } IntSize ScrollView::unscaledVisibleContentSize(IncludeScrollbarsInRect scrollbarInclusion) const { return scrollbarInclusion == ExcludeScrollbars ? excludeScrollbars(frameRect().size()) : frameRect().size(); } IntSize ScrollView::excludeScrollbars(const IntSize& size) const { int verticalScrollbarWidth = 0; int horizontalScrollbarHeight = 0; if (Scrollbar* verticalBar = verticalScrollbar()) verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0; if (Scrollbar* horizontalBar = horizontalScrollbar()) horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0; return IntSize(max(0, size.width() - verticalScrollbarWidth), max(0, size.height() - horizontalScrollbarHeight)); } IntRect ScrollView::visibleContentRect(IncludeScrollbarsInRect scollbarInclusion) const { FloatSize visibleContentSize = unscaledVisibleContentSize(scollbarInclusion); visibleContentSize.scale(1 / visibleContentScaleFactor()); return IntRect(IntPoint(m_scrollOffset), expandedIntSize(visibleContentSize)); } IntSize ScrollView::contentsSize() const { return m_contentsSize; } void ScrollView::setContentsSize(const IntSize& newSize) { if (contentsSize() == newSize) return; m_contentsSize = newSize; updateScrollbars(scrollOffset()); updateOverhangAreas(); } IntPoint ScrollView::maximumScrollPosition() const { IntPoint maximumOffset(contentsWidth() - visibleWidth() - scrollOrigin().x(), contentsHeight() - visibleHeight() - scrollOrigin().y()); maximumOffset.clampNegativeToZero(); return maximumOffset; } IntPoint ScrollView::minimumScrollPosition() const { return IntPoint(-scrollOrigin().x(), -scrollOrigin().y()); } IntPoint ScrollView::adjustScrollPositionWithinRange(const IntPoint& scrollPoint) const { if (!constrainsScrollingToContentEdge()) return scrollPoint; IntPoint newScrollPosition = scrollPoint.shrunkTo(maximumScrollPosition()); newScrollPosition = newScrollPosition.expandedTo(minimumScrollPosition()); return newScrollPosition; } int ScrollView::scrollSize(ScrollbarOrientation orientation) const { Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_horizontalScrollbar : m_verticalScrollbar).get(); // If no scrollbars are present, the content may still be scrollable. if (!scrollbar) { IntSize scrollSize = m_contentsSize - visibleContentRect().size(); scrollSize.clampNegativeToZero(); return orientation == HorizontalScrollbar ? scrollSize.width() : scrollSize.height(); } return scrollbar->totalSize() - scrollbar->visibleSize(); } void ScrollView::notifyPageThatContentAreaWillPaint() const { } void ScrollView::setScrollOffset(const IntPoint& offset) { scrollTo(toIntSize(adjustScrollPositionWithinRange(offset))); } void ScrollView::scrollTo(const IntSize& newOffset) { IntSize scrollDelta = newOffset - m_scrollOffset; if (scrollDelta == IntSize()) return; m_scrollOffset = newOffset; if (scrollbarsSuppressed()) return; repaintFixedElementsAfterScrolling(); scrollContents(scrollDelta); updateFixedElementsAfterScrolling(); } void ScrollView::setScrollPosition(const IntPoint& scrollPoint) { IntPoint newScrollPosition = adjustScrollPositionWithinRange(scrollPoint); if (newScrollPosition == scrollPosition()) return; updateScrollbars(IntSize(newScrollPosition.x(), newScrollPosition.y())); } bool ScrollView::scroll(ScrollDirection direction, ScrollGranularity granularity) { ScrollDirection physicalDirection = toPhysicalDirection(direction, isVerticalDocument(), isFlippedDocument()); return ScrollableArea::scroll(physicalDirection, granularity); } IntSize ScrollView::overhangAmount() const { IntSize stretch; IntPoint currentScrollPosition = scrollPosition(); IntPoint minScrollPosition = minimumScrollPosition(); IntPoint maxScrollPosition = maximumScrollPosition(); if (currentScrollPosition.x() < minScrollPosition.x()) stretch.setWidth(currentScrollPosition.x() - minScrollPosition.x()); if (currentScrollPosition.x() > maxScrollPosition.x()) stretch.setWidth(currentScrollPosition.x() - maxScrollPosition.x()); if (currentScrollPosition.y() < minScrollPosition.y()) stretch.setHeight(currentScrollPosition.y() - minScrollPosition.y()); if (currentScrollPosition.y() > maxScrollPosition.y()) stretch.setHeight(currentScrollPosition.y() - maxScrollPosition.y()); return stretch; } void ScrollView::windowResizerRectChanged() { updateScrollbars(scrollOffset()); } static const unsigned cMaxUpdateScrollbarsPass = 2; void ScrollView::updateScrollbars(const IntSize& desiredOffset) { if (m_inUpdateScrollbars) return; // If we came in here with the view already needing a layout, then go ahead and do that // first. (This will be the common case, e.g., when the page changes due to window resizing for example). // This layout will not re-enter updateScrollbars and does not count towards our max layout pass total. if (!m_scrollbarsSuppressed) { m_inUpdateScrollbars = true; scrollbarExistenceDidChange(); m_inUpdateScrollbars = false; } IntRect oldScrollCornerRect = scrollCornerRect(); bool hasHorizontalScrollbar = m_horizontalScrollbar; bool hasVerticalScrollbar = m_verticalScrollbar; bool newHasHorizontalScrollbar = hasHorizontalScrollbar; bool newHasVerticalScrollbar = hasVerticalScrollbar; ScrollbarMode hScroll = m_horizontalScrollbarMode; ScrollbarMode vScroll = m_verticalScrollbarMode; if (hScroll != ScrollbarAuto) newHasHorizontalScrollbar = (hScroll == ScrollbarAlwaysOn); if (vScroll != ScrollbarAuto) newHasVerticalScrollbar = (vScroll == ScrollbarAlwaysOn); if (m_scrollbarsSuppressed || (hScroll != ScrollbarAuto && vScroll != ScrollbarAuto)) { if (hasHorizontalScrollbar != newHasHorizontalScrollbar) setHasHorizontalScrollbar(newHasHorizontalScrollbar); if (hasVerticalScrollbar != newHasVerticalScrollbar) setHasVerticalScrollbar(newHasVerticalScrollbar); } else { bool scrollbarExistenceChanged = false; IntSize docSize = contentsSize(); IntSize fullVisibleSize = visibleContentRect(IncludeScrollbars).size(); bool scrollbarsAreOverlay = ScrollbarTheme::theme()->usesOverlayScrollbars(); if (hScroll == ScrollbarAuto) { newHasHorizontalScrollbar = docSize.width() > visibleWidth(); if (!scrollbarsAreOverlay && newHasHorizontalScrollbar && !m_updateScrollbarsPass && docSize.width() <= fullVisibleSize.width() && docSize.height() <= fullVisibleSize.height()) newHasHorizontalScrollbar = false; } if (vScroll == ScrollbarAuto) { newHasVerticalScrollbar = docSize.height() > visibleHeight(); if (!scrollbarsAreOverlay && newHasVerticalScrollbar && !m_updateScrollbarsPass && docSize.width() <= fullVisibleSize.width() && docSize.height() <= fullVisibleSize.height()) newHasVerticalScrollbar = false; } if (!scrollbarsAreOverlay) { // If we ever turn one scrollbar off, always turn the other one off too. Never ever // try to both gain/lose a scrollbar in the same pass. if (!newHasHorizontalScrollbar && hasHorizontalScrollbar && vScroll != ScrollbarAlwaysOn) newHasVerticalScrollbar = false; if (!newHasVerticalScrollbar && hasVerticalScrollbar && hScroll != ScrollbarAlwaysOn) newHasHorizontalScrollbar = false; } if (hasHorizontalScrollbar != newHasHorizontalScrollbar) { scrollbarExistenceChanged = true; if (scrollOrigin().y() && !newHasHorizontalScrollbar && !scrollbarsAreOverlay) ScrollableArea::setScrollOrigin(IntPoint(scrollOrigin().x(), scrollOrigin().y() - m_horizontalScrollbar->height())); if (hasHorizontalScrollbar) m_horizontalScrollbar->invalidate(); setHasHorizontalScrollbar(newHasHorizontalScrollbar); } if (hasVerticalScrollbar != newHasVerticalScrollbar) { scrollbarExistenceChanged = true; if (scrollOrigin().x() && !newHasVerticalScrollbar && !scrollbarsAreOverlay) ScrollableArea::setScrollOrigin(IntPoint(scrollOrigin().x() - m_verticalScrollbar->width(), scrollOrigin().y())); if (hasVerticalScrollbar) m_verticalScrollbar->invalidate(); setHasVerticalScrollbar(newHasVerticalScrollbar); } if (scrollbarExistenceChanged) { if (scrollbarsAreOverlay) { // Synchronize status of scrollbar layers if necessary. m_inUpdateScrollbars = true; scrollbarExistenceDidChange(); m_inUpdateScrollbars = false; } else if (m_updateScrollbarsPass < cMaxUpdateScrollbarsPass) { m_updateScrollbarsPass++; contentsResized(); scrollbarExistenceDidChange(); IntSize newDocSize = contentsSize(); if (newDocSize == docSize) { // The layout with the new scroll state had no impact on // the document's overall size, so updateScrollbars didn't get called. // Recur manually. updateScrollbars(desiredOffset); } m_updateScrollbarsPass--; } } } // Set up the range, but only do this if we're not in a nested call (to avoid // doing it multiple times). if (m_updateScrollbarsPass) return; m_inUpdateScrollbars = true; if (m_horizontalScrollbar) { int clientWidth = visibleWidth(); IntRect oldRect(m_horizontalScrollbar->frameRect()); IntRect hBarRect((shouldPlaceVerticalScrollbarOnLeft() && m_verticalScrollbar) ? m_verticalScrollbar->width() : 0, height() - m_horizontalScrollbar->height(), width() - (m_verticalScrollbar ? m_verticalScrollbar->width() : 0), m_horizontalScrollbar->height()); m_horizontalScrollbar->setFrameRect(hBarRect); if (!m_scrollbarsSuppressed && oldRect != m_horizontalScrollbar->frameRect()) m_horizontalScrollbar->invalidate(); if (m_scrollbarsSuppressed) m_horizontalScrollbar->setSuppressInvalidation(true); m_horizontalScrollbar->setEnabled(contentsWidth() > clientWidth); m_horizontalScrollbar->setProportion(clientWidth, contentsWidth()); if (m_scrollbarsSuppressed) m_horizontalScrollbar->setSuppressInvalidation(false); } if (m_verticalScrollbar) { int clientHeight = visibleHeight(); IntRect oldRect(m_verticalScrollbar->frameRect()); IntRect vBarRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m_verticalScrollbar->width()), 0, m_verticalScrollbar->width(), height() - (m_horizontalScrollbar ? m_horizontalScrollbar->height() : 0)); m_verticalScrollbar->setFrameRect(vBarRect); if (!m_scrollbarsSuppressed && oldRect != m_verticalScrollbar->frameRect()) m_verticalScrollbar->invalidate(); if (m_scrollbarsSuppressed) m_verticalScrollbar->setSuppressInvalidation(true); m_verticalScrollbar->setEnabled(contentsHeight() > clientHeight); m_verticalScrollbar->setProportion(clientHeight, contentsHeight()); if (m_scrollbarsSuppressed) m_verticalScrollbar->setSuppressInvalidation(false); } if (hasHorizontalScrollbar != newHasHorizontalScrollbar || hasVerticalScrollbar != newHasVerticalScrollbar) { // FIXME: Is frameRectsChanged really necessary here? Have any frame rects changed? frameRectsChanged(); positionScrollbarLayers(); updateScrollCorner(); if (!m_horizontalScrollbar && !m_verticalScrollbar) invalidateScrollCornerRect(oldScrollCornerRect); } IntPoint adjustedScrollPosition = IntPoint(desiredOffset); if (!isRubberBandInProgress()) adjustedScrollPosition = adjustScrollPositionWithinRange(adjustedScrollPosition); if (adjustedScrollPosition != scrollPosition() || scrollOriginChanged()) { ScrollableArea::scrollToOffsetWithoutAnimation(adjustedScrollPosition); resetScrollOriginChanged(); } // Make sure the scrollbar offsets are up to date. if (m_horizontalScrollbar) m_horizontalScrollbar->offsetDidChange(); if (m_verticalScrollbar) m_verticalScrollbar->offsetDidChange(); m_inUpdateScrollbars = false; } const int panIconSizeLength = 16; IntRect ScrollView::rectToCopyOnScroll() const { IntRect scrollViewRect = convertToRootView(IntRect((shouldPlaceVerticalScrollbarOnLeft() && verticalScrollbar()) ? verticalScrollbar()->width() : 0, 0, visibleWidth(), visibleHeight())); if (hasOverlayScrollbars()) { int verticalScrollbarWidth = (verticalScrollbar() && !hasLayerForVerticalScrollbar()) ? verticalScrollbar()->width() : 0; int horizontalScrollbarHeight = (horizontalScrollbar() && !hasLayerForHorizontalScrollbar()) ? horizontalScrollbar()->height() : 0; scrollViewRect.setWidth(scrollViewRect.width() - verticalScrollbarWidth); scrollViewRect.setHeight(scrollViewRect.height() - horizontalScrollbarHeight); } return scrollViewRect; } void ScrollView::scrollContents(const IntSize& scrollDelta) { HostWindow* window = hostWindow(); if (!window) return; // Since scrolling is double buffered, we will be blitting the scroll view's intersection // with the clip rect every time to keep it smooth. IntRect clipRect = windowClipRect(); IntRect scrollViewRect = rectToCopyOnScroll(); IntRect updateRect = clipRect; updateRect.intersect(scrollViewRect); if (m_drawPanScrollIcon) { // FIXME: the pan icon is broken when accelerated compositing is on, since it will draw under the compositing layers. // https://bugs.webkit.org/show_bug.cgi?id=47837 int panIconDirtySquareSizeLength = 2 * (panIconSizeLength + max(abs(scrollDelta.width()), abs(scrollDelta.height()))); // We only want to repaint what's necessary IntPoint panIconDirtySquareLocation = IntPoint(m_panScrollIconPoint.x() - (panIconDirtySquareSizeLength / 2), m_panScrollIconPoint.y() - (panIconDirtySquareSizeLength / 2)); IntRect panScrollIconDirtyRect = IntRect(panIconDirtySquareLocation, IntSize(panIconDirtySquareSizeLength, panIconDirtySquareSizeLength)); panScrollIconDirtyRect.intersect(clipRect); window->invalidateContentsAndRootView(panScrollIconDirtyRect); } if (canBlitOnScroll()) { // The main frame can just blit the WebView window // FIXME: Find a way to scroll subframes with this faster path if (!scrollContentsFastPath(-scrollDelta, scrollViewRect, clipRect)) scrollContentsSlowPath(updateRect); } else { // We need to go ahead and repaint the entire backing store. Do it now before moving the // windowed plugins. scrollContentsSlowPath(updateRect); } // Invalidate the overhang areas if they are visible. updateOverhangAreas(); // This call will move children with native widgets (plugins) and invalidate them as well. frameRectsChanged(); } bool ScrollView::scrollContentsFastPath(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect) { hostWindow()->scroll(scrollDelta, rectToScroll, clipRect); return true; } void ScrollView::scrollContentsSlowPath(const IntRect& updateRect) { hostWindow()->invalidateContentsForSlowScroll(updateRect); } IntPoint ScrollView::rootViewToContents(const IntPoint& rootViewPoint) const { IntPoint viewPoint = convertFromRootView(rootViewPoint); return viewPoint + scrollOffset(); } IntPoint ScrollView::contentsToRootView(const IntPoint& contentsPoint) const { IntPoint viewPoint = contentsPoint - scrollOffset(); return convertToRootView(viewPoint); } IntRect ScrollView::rootViewToContents(const IntRect& rootViewRect) const { IntRect viewRect = convertFromRootView(rootViewRect); viewRect.move(scrollOffset()); return viewRect; } IntRect ScrollView::contentsToRootView(const IntRect& contentsRect) const { IntRect viewRect = contentsRect; viewRect.move(-scrollOffset()); return convertToRootView(viewRect); } IntPoint ScrollView::windowToContents(const IntPoint& windowPoint) const { IntPoint viewPoint = convertFromContainingWindow(windowPoint); return viewPoint + scrollOffset(); } IntPoint ScrollView::contentsToWindow(const IntPoint& contentsPoint) const { IntPoint viewPoint = contentsPoint - scrollOffset(); return convertToContainingWindow(viewPoint); } IntRect ScrollView::windowToContents(const IntRect& windowRect) const { IntRect viewRect = convertFromContainingWindow(windowRect); viewRect.move(scrollOffset()); return viewRect; } IntRect ScrollView::contentsToWindow(const IntRect& contentsRect) const { IntRect viewRect = contentsRect; viewRect.move(-scrollOffset()); return convertToContainingWindow(viewRect); } IntRect ScrollView::contentsToScreen(const IntRect& rect) const { HostWindow* window = hostWindow(); if (!window) return IntRect(); return window->rootViewToScreen(contentsToRootView(rect)); } IntPoint ScrollView::screenToContents(const IntPoint& point) const { HostWindow* window = hostWindow(); if (!window) return IntPoint(); return rootViewToContents(window->screenToRootView(point)); } bool ScrollView::containsScrollbarsAvoidingResizer() const { return !m_scrollbarsAvoidingResizer; } void ScrollView::adjustScrollbarsAvoidingResizerCount(int overlapDelta) { int oldCount = m_scrollbarsAvoidingResizer; m_scrollbarsAvoidingResizer += overlapDelta; if (parent()) toScrollView(parent())->adjustScrollbarsAvoidingResizerCount(overlapDelta); else if (!scrollbarsSuppressed()) { // If we went from n to 0 or from 0 to n and we're the outermost view, // we need to invalidate the windowResizerRect(), since it will now need to paint // differently. if ((oldCount > 0 && m_scrollbarsAvoidingResizer == 0) || (oldCount == 0 && m_scrollbarsAvoidingResizer > 0)) invalidateRect(windowResizerRect()); } } void ScrollView::setParent(Widget* parentView) { if (parentView == parent()) return; if (m_scrollbarsAvoidingResizer && parent()) toScrollView(parent())->adjustScrollbarsAvoidingResizerCount(-m_scrollbarsAvoidingResizer); Widget::setParent(parentView); if (m_scrollbarsAvoidingResizer && parent()) toScrollView(parent())->adjustScrollbarsAvoidingResizerCount(m_scrollbarsAvoidingResizer); } void ScrollView::setScrollbarsSuppressed(bool suppressed, bool repaintOnUnsuppress) { if (suppressed == m_scrollbarsSuppressed) return; m_scrollbarsSuppressed = suppressed; if (repaintOnUnsuppress && !suppressed) { if (m_horizontalScrollbar) m_horizontalScrollbar->invalidate(); if (m_verticalScrollbar) m_verticalScrollbar->invalidate(); // Invalidate the scroll corner too on unsuppress. invalidateRect(scrollCornerRect()); } } Scrollbar* ScrollView::scrollbarAtPoint(const IntPoint& windowPoint) { IntPoint viewPoint = convertFromContainingWindow(windowPoint); if (m_horizontalScrollbar && m_horizontalScrollbar->shouldParticipateInHitTesting() && m_horizontalScrollbar->frameRect().contains(viewPoint)) return m_horizontalScrollbar.get(); if (m_verticalScrollbar && m_verticalScrollbar->shouldParticipateInHitTesting() && m_verticalScrollbar->frameRect().contains(viewPoint)) return m_verticalScrollbar.get(); return 0; } void ScrollView::setFrameRect(const IntRect& newRect) { IntRect oldRect = frameRect(); if (newRect == oldRect) return; Widget::setFrameRect(newRect); updateScrollbars(scrollOffset()); frameRectsChanged(); } void ScrollView::frameRectsChanged() { HashSet >::const_iterator end = m_children.end(); for (HashSet >::const_iterator current = m_children.begin(); current != end; ++current) (*current)->frameRectsChanged(); } void ScrollView::clipRectChanged() { HashSet >::const_iterator end = m_children.end(); for (HashSet >::const_iterator current = m_children.begin(); current != end; ++current) (*current)->clipRectChanged(); } static void positionScrollbarLayer(GraphicsLayer* graphicsLayer, Scrollbar* scrollbar) { if (!graphicsLayer || !scrollbar) return; IntRect scrollbarRect = scrollbar->frameRect(); graphicsLayer->setPosition(scrollbarRect.location()); if (scrollbarRect.size() == graphicsLayer->size()) return; graphicsLayer->setSize(scrollbarRect.size()); if (graphicsLayer->hasContentsLayer()) { graphicsLayer->setContentsRect(IntRect(0, 0, scrollbarRect.width(), scrollbarRect.height())); return; } graphicsLayer->setDrawsContent(true); graphicsLayer->setNeedsDisplay(); } static void positionScrollCornerLayer(GraphicsLayer* graphicsLayer, const IntRect& cornerRect) { if (!graphicsLayer) return; graphicsLayer->setDrawsContent(!cornerRect.isEmpty()); graphicsLayer->setPosition(cornerRect.location()); if (cornerRect.size() != graphicsLayer->size()) graphicsLayer->setNeedsDisplay(); graphicsLayer->setSize(cornerRect.size()); } void ScrollView::positionScrollbarLayers() { positionScrollbarLayer(layerForHorizontalScrollbar(), horizontalScrollbar()); positionScrollbarLayer(layerForVerticalScrollbar(), verticalScrollbar()); positionScrollCornerLayer(layerForScrollCorner(), scrollCornerRect()); } bool ScrollView::userInputScrollable(ScrollbarOrientation orientation) const { ScrollbarMode mode = (orientation == HorizontalScrollbar) ? m_horizontalScrollbarMode : m_verticalScrollbarMode; return mode == ScrollbarAuto || mode == ScrollbarAlwaysOn; } bool ScrollView::shouldPlaceVerticalScrollbarOnLeft() const { return false; } void ScrollView::repaintContentRectangle(const IntRect& rect) { IntRect paintRect = rect; if (clipsRepaints() && !paintsEntireContents()) paintRect.intersect(visibleContentRect()); if (paintRect.isEmpty()) return; if (HostWindow* window = hostWindow()) window->invalidateContentsAndRootView(contentsToWindow(paintRect)); } IntRect ScrollView::scrollCornerRect() const { IntRect cornerRect; if (hasOverlayScrollbars()) return cornerRect; if (m_horizontalScrollbar && width() - m_horizontalScrollbar->width() > 0) { cornerRect.unite(IntRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : m_horizontalScrollbar->width(), height() - m_horizontalScrollbar->height(), width() - m_horizontalScrollbar->width(), m_horizontalScrollbar->height())); } if (m_verticalScrollbar && height() - m_verticalScrollbar->height() > 0) { cornerRect.unite(IntRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m_verticalScrollbar->width()), m_verticalScrollbar->height(), m_verticalScrollbar->width(), height() - m_verticalScrollbar->height())); } return cornerRect; } bool ScrollView::isScrollCornerVisible() const { return !scrollCornerRect().isEmpty(); } void ScrollView::scrollbarStyleChanged(int, bool forceUpdate) { if (!forceUpdate) return; contentsResized(); updateScrollbars(scrollOffset()); positionScrollbarLayers(); } void ScrollView::updateScrollCorner() { } void ScrollView::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect) { ScrollbarTheme::theme()->paintScrollCorner(context, cornerRect); } void ScrollView::paintScrollbar(GraphicsContext* context, Scrollbar* bar, const IntRect& rect) { bar->paint(context, rect); } void ScrollView::invalidateScrollCornerRect(const IntRect& rect) { invalidateRect(rect); } void ScrollView::paintScrollbars(GraphicsContext* context, const IntRect& rect) { if (m_horizontalScrollbar && !layerForHorizontalScrollbar()) paintScrollbar(context, m_horizontalScrollbar.get(), rect); if (m_verticalScrollbar && !layerForVerticalScrollbar()) paintScrollbar(context, m_verticalScrollbar.get(), rect); if (layerForScrollCorner()) return; paintScrollCorner(context, scrollCornerRect()); } void ScrollView::paintPanScrollIcon(GraphicsContext* context) { DEFINE_STATIC_REF(Image, panScrollIcon, (Image::loadPlatformResource("panIcon"))); IntPoint iconGCPoint = m_panScrollIconPoint; if (parent()) iconGCPoint = toScrollView(parent())->windowToContents(iconGCPoint); context->drawImage(panScrollIcon, iconGCPoint); } void ScrollView::paint(GraphicsContext* context, const IntRect& rect) { if (context->paintingDisabled() && !context->updatingControlTints()) return; notifyPageThatContentAreaWillPaint(); IntRect documentDirtyRect = rect; if (!paintsEntireContents()) { IntRect visibleAreaWithoutScrollbars(location(), visibleContentRect().size()); documentDirtyRect.intersect(visibleAreaWithoutScrollbars); } if (!documentDirtyRect.isEmpty()) { GraphicsContextStateSaver stateSaver(*context); context->translate(x(), y()); documentDirtyRect.moveBy(-location()); if (!paintsEntireContents()) { context->translate(-scrollX(), -scrollY()); documentDirtyRect.moveBy(scrollPosition()); context->clip(visibleContentRect()); } paintContents(context, documentDirtyRect); } calculateAndPaintOverhangAreas(context, rect); // Now paint the scrollbars. if (!m_scrollbarsSuppressed && (m_horizontalScrollbar || m_verticalScrollbar)) { GraphicsContextStateSaver stateSaver(*context); IntRect scrollViewDirtyRect = rect; IntRect visibleAreaWithScrollbars(location(), visibleContentRect(IncludeScrollbars).size()); scrollViewDirtyRect.intersect(visibleAreaWithScrollbars); context->translate(x(), y()); scrollViewDirtyRect.moveBy(-location()); paintScrollbars(context, scrollViewDirtyRect); } // Paint the panScroll Icon if (m_drawPanScrollIcon) paintPanScrollIcon(context); } void ScrollView::calculateOverhangAreasForPainting(IntRect& horizontalOverhangRect, IntRect& verticalOverhangRect) { int verticalScrollbarWidth = (verticalScrollbar() && !verticalScrollbar()->isOverlayScrollbar()) ? verticalScrollbar()->width() : 0; int horizontalScrollbarHeight = (horizontalScrollbar() && !horizontalScrollbar()->isOverlayScrollbar()) ? horizontalScrollbar()->height() : 0; int physicalScrollY = scrollPosition().y() + scrollOrigin().y(); if (physicalScrollY < 0) { horizontalOverhangRect = frameRect(); horizontalOverhangRect.setHeight(-physicalScrollY); horizontalOverhangRect.setWidth(horizontalOverhangRect.width() - verticalScrollbarWidth); } else if (contentsHeight() && physicalScrollY > contentsHeight() - visibleHeight()) { int height = physicalScrollY - (contentsHeight() - visibleHeight()); horizontalOverhangRect = frameRect(); horizontalOverhangRect.setY(frameRect().maxY() - height - horizontalScrollbarHeight); horizontalOverhangRect.setHeight(height); horizontalOverhangRect.setWidth(horizontalOverhangRect.width() - verticalScrollbarWidth); } int physicalScrollX = scrollPosition().x() + scrollOrigin().x(); if (physicalScrollX < 0) { verticalOverhangRect.setWidth(-physicalScrollX); verticalOverhangRect.setHeight(frameRect().height() - horizontalOverhangRect.height() - horizontalScrollbarHeight); verticalOverhangRect.setX(frameRect().x()); if (horizontalOverhangRect.y() == frameRect().y()) verticalOverhangRect.setY(frameRect().y() + horizontalOverhangRect.height()); else verticalOverhangRect.setY(frameRect().y()); } else if (contentsWidth() && physicalScrollX > contentsWidth() - visibleWidth()) { int width = physicalScrollX - (contentsWidth() - visibleWidth()); verticalOverhangRect.setWidth(width); verticalOverhangRect.setHeight(frameRect().height() - horizontalOverhangRect.height() - horizontalScrollbarHeight); verticalOverhangRect.setX(frameRect().maxX() - width - verticalScrollbarWidth); if (horizontalOverhangRect.y() == frameRect().y()) verticalOverhangRect.setY(frameRect().y() + horizontalOverhangRect.height()); else verticalOverhangRect.setY(frameRect().y()); } } void ScrollView::updateOverhangAreas() { HostWindow* window = hostWindow(); if (!window) return; IntRect horizontalOverhangRect; IntRect verticalOverhangRect; calculateOverhangAreasForPainting(horizontalOverhangRect, verticalOverhangRect); if (!horizontalOverhangRect.isEmpty()) window->invalidateContentsAndRootView(horizontalOverhangRect); if (!verticalOverhangRect.isEmpty()) window->invalidateContentsAndRootView(verticalOverhangRect); } void ScrollView::paintOverhangAreas(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect) { ScrollbarTheme::theme()->paintOverhangBackground(context, horizontalOverhangRect, verticalOverhangRect, dirtyRect); ScrollbarTheme::theme()->paintOverhangShadows(context, scrollOffset(), horizontalOverhangRect, verticalOverhangRect, dirtyRect); } void ScrollView::calculateAndPaintOverhangAreas(GraphicsContext* context, const IntRect& dirtyRect) { IntRect horizontalOverhangRect; IntRect verticalOverhangRect; calculateOverhangAreasForPainting(horizontalOverhangRect, verticalOverhangRect); if (dirtyRect.intersects(horizontalOverhangRect) || dirtyRect.intersects(verticalOverhangRect)) paintOverhangAreas(context, horizontalOverhangRect, verticalOverhangRect, dirtyRect); } void ScrollView::calculateAndPaintOverhangBackground(GraphicsContext* context, const IntRect& dirtyRect) { IntRect horizontalOverhangRect; IntRect verticalOverhangRect; calculateOverhangAreasForPainting(horizontalOverhangRect, verticalOverhangRect); if (dirtyRect.intersects(horizontalOverhangRect) || dirtyRect.intersects(verticalOverhangRect)) ScrollbarTheme::theme()->paintOverhangBackground(context, horizontalOverhangRect, verticalOverhangRect, dirtyRect); } bool ScrollView::isPointInScrollbarCorner(const IntPoint& windowPoint) { if (!scrollbarCornerPresent()) return false; IntPoint viewPoint = convertFromContainingWindow(windowPoint); if (m_horizontalScrollbar) { int horizontalScrollbarYMin = m_horizontalScrollbar->frameRect().y(); int horizontalScrollbarYMax = m_horizontalScrollbar->frameRect().y() + m_horizontalScrollbar->frameRect().height(); int horizontalScrollbarXMin = m_horizontalScrollbar->frameRect().x() + m_horizontalScrollbar->frameRect().width(); return viewPoint.y() > horizontalScrollbarYMin && viewPoint.y() < horizontalScrollbarYMax && viewPoint.x() > horizontalScrollbarXMin; } int verticalScrollbarXMin = m_verticalScrollbar->frameRect().x(); int verticalScrollbarXMax = m_verticalScrollbar->frameRect().x() + m_verticalScrollbar->frameRect().width(); int verticalScrollbarYMin = m_verticalScrollbar->frameRect().y() + m_verticalScrollbar->frameRect().height(); return viewPoint.x() > verticalScrollbarXMin && viewPoint.x() < verticalScrollbarXMax && viewPoint.y() > verticalScrollbarYMin; } bool ScrollView::scrollbarCornerPresent() const { return (m_horizontalScrollbar && width() - m_horizontalScrollbar->width() > 0) || (m_verticalScrollbar && height() - m_verticalScrollbar->height() > 0); } IntRect ScrollView::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& localRect) const { // Scrollbars won't be transformed within us IntRect newRect = localRect; newRect.moveBy(scrollbar->location()); return newRect; } IntRect ScrollView::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const { IntRect newRect = parentRect; // Scrollbars won't be transformed within us newRect.moveBy(-scrollbar->location()); return newRect; } // FIXME: test these on windows IntPoint ScrollView::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& localPoint) const { // Scrollbars won't be transformed within us IntPoint newPoint = localPoint; newPoint.moveBy(scrollbar->location()); return newPoint; } IntPoint ScrollView::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const { IntPoint newPoint = parentPoint; // Scrollbars won't be transformed within us newPoint.moveBy(-scrollbar->location()); return newPoint; } void ScrollView::setParentVisible(bool visible) { if (isParentVisible() == visible) return; Widget::setParentVisible(visible); if (!isSelfVisible()) return; HashSet >::iterator end = m_children.end(); for (HashSet >::iterator it = m_children.begin(); it != end; ++it) (*it)->setParentVisible(visible); } void ScrollView::show() { if (!isSelfVisible()) { setSelfVisible(true); if (isParentVisible()) { HashSet >::iterator end = m_children.end(); for (HashSet >::iterator it = m_children.begin(); it != end; ++it) (*it)->setParentVisible(true); } } Widget::show(); } void ScrollView::hide() { if (isSelfVisible()) { if (isParentVisible()) { HashSet >::iterator end = m_children.end(); for (HashSet >::iterator it = m_children.begin(); it != end; ++it) (*it)->setParentVisible(false); } setSelfVisible(false); } Widget::hide(); } bool ScrollView::isOffscreen() const { return !isVisible(); } void ScrollView::addPanScrollIcon(const IntPoint& iconPosition) { HostWindow* window = hostWindow(); if (!window) return; m_drawPanScrollIcon = true; m_panScrollIconPoint = IntPoint(iconPosition.x() - panIconSizeLength / 2 , iconPosition.y() - panIconSizeLength / 2) ; window->invalidateContentsAndRootView(IntRect(m_panScrollIconPoint, IntSize(panIconSizeLength, panIconSizeLength))); } void ScrollView::removePanScrollIcon() { HostWindow* window = hostWindow(); if (!window) return; m_drawPanScrollIcon = false; window->invalidateContentsAndRootView(IntRect(m_panScrollIconPoint, IntSize(panIconSizeLength, panIconSizeLength))); } void ScrollView::setScrollOrigin(const IntPoint& origin, bool updatePositionAtAll, bool updatePositionSynchronously) { if (scrollOrigin() == origin) return; ScrollableArea::setScrollOrigin(origin); // Update if the scroll origin changes, since our position will be different if the content size did not change. if (updatePositionAtAll && updatePositionSynchronously) updateScrollbars(scrollOffset()); } int ScrollView::pageStep(ScrollbarOrientation orientation) const { int length = (orientation == HorizontalScrollbar) ? visibleWidth() : visibleHeight(); int minPageStep = static_cast(length) * minFractionToStepWhenPaging(); int pageStep = std::max(minPageStep, length - maxOverlapBetweenPages()); return std::max(pageStep, 1); } } // namespace WebCore