/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Neil Cronin (neil@rackle.com) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsScrollPortView.h" #include "nsIWidget.h" #include "nsIDeviceContext.h" #include "nsGUIEvent.h" #include "nsWidgetsCID.h" #include "nsViewsCID.h" #include "nsIScrollableView.h" #include "nsILookAndFeel.h" #include "nsISupportsArray.h" #include "nsIScrollPositionListener.h" #include "nsIRegion.h" #include "nsViewManager.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsCOMPtr.h" #include "nsServiceManagerUtils.h" #include static NS_DEFINE_IID(kWidgetCID, NS_CHILD_CID); #define SMOOTH_SCROLL_MSECS_PER_FRAME 10 #define SMOOTH_SCROLL_FRAMES 10 #define SMOOTH_SCROLL_PREF_NAME "general.smoothScroll" class AsyncScroll { public: AsyncScroll() {} ~AsyncScroll() { if (mScrollTimer) mScrollTimer->Cancel(); } nsCOMPtr mScrollTimer; PRInt32 mVelocities[SMOOTH_SCROLL_FRAMES*2]; PRInt32 mFrameIndex; PRPackedBool mIsSmoothScroll; }; nsScrollPortView::nsScrollPortView(nsViewManager* aViewManager) : nsView(aViewManager) { mOffsetX = mOffsetY = 0; mDestinationX = mDestinationY = 0; nsCOMPtr dev; mViewManager->GetDeviceContext(*getter_AddRefs(dev)); mLineHeight = dev->AppUnitsPerInch() / 6; // 12 pt mListeners = nsnull; mAsyncScroll = nsnull; } nsScrollPortView::~nsScrollPortView() { if (nsnull != mListeners) { mListeners->Clear(); NS_RELEASE(mListeners); } if (nsnull != mViewManager) { nsIScrollableView* scrollingView; mViewManager->GetRootScrollableView(&scrollingView); if ((nsnull != scrollingView) && (this == scrollingView)) { mViewManager->SetRootScrollableView(nsnull); } } delete mAsyncScroll; } nsresult nsScrollPortView::QueryInterface(const nsIID& aIID, void** aInstancePtr) { if (nsnull == aInstancePtr) { return NS_ERROR_NULL_POINTER; } *aInstancePtr = nsnull; if (aIID.Equals(NS_GET_IID(nsIScrollableView))) { *aInstancePtr = (void*)(nsIScrollableView*)this; return NS_OK; } return nsView::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP_(nsIView*) nsScrollPortView::View() { return this; } NS_IMETHODIMP nsScrollPortView::AddScrollPositionListener(nsIScrollPositionListener* aListener) { if (nsnull == mListeners) { nsresult rv = NS_NewISupportsArray(&mListeners); if (NS_FAILED(rv)) return rv; } return mListeners->AppendElement(aListener); } NS_IMETHODIMP nsScrollPortView::RemoveScrollPositionListener(nsIScrollPositionListener* aListener) { if (nsnull != mListeners) { return mListeners->RemoveElement(aListener); } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsScrollPortView::GetContainerSize(nscoord *aWidth, nscoord *aHeight) const { if (!aWidth || !aHeight) return NS_ERROR_NULL_POINTER; *aWidth = 0; *aHeight = 0; nsView *scrolledView = GetScrolledView(); if (!scrolledView) return NS_ERROR_FAILURE; nsSize sz; scrolledView->GetDimensions(sz); *aWidth = sz.width; *aHeight = sz.height; return NS_OK; } static void ComputeVelocities(PRInt32 aCurVelocity, nscoord aCurPos, nscoord aDstPos, PRInt32* aVelocities, PRInt32 aP2A) { // scrolling always works in units of whole pixels. So compute velocities // in pixels and then scale them up. This ensures, for example, that // a 1-pixel scroll isn't broken into N frames of 1/N pixels each, each // frame increment being rounded to 0 whole pixels. aCurPos = NSAppUnitsToIntPixels(aCurPos, aP2A); aDstPos = NSAppUnitsToIntPixels(aDstPos, aP2A); PRInt32 i; PRInt32 direction = (aCurPos < aDstPos ? 1 : -1); PRInt32 absDelta = (aDstPos - aCurPos)*direction; PRInt32 baseVelocity = absDelta/SMOOTH_SCROLL_FRAMES; for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) { aVelocities[i*2] = baseVelocity; } nscoord total = baseVelocity*SMOOTH_SCROLL_FRAMES; for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) { if (total < absDelta) { aVelocities[i*2]++; total++; } } NS_ASSERTION(total == absDelta, "Invalid velocity sum"); PRInt32 scale = NSIntPixelsToAppUnits(direction, aP2A); for (i = 0; i < SMOOTH_SCROLL_FRAMES; i++) { aVelocities[i*2] *= scale; } } static nsresult ClampScrollValues(nscoord& aX, nscoord& aY, nsScrollPortView* aThis) { // make sure the new position is in bounds nsView* scrolledView = aThis->GetScrolledView(); if (!scrolledView) return NS_ERROR_FAILURE; nsRect scrolledRect; scrolledView->GetDimensions(scrolledRect); nsSize portSize; aThis->GetDimensions(portSize); nscoord maxX = scrolledRect.XMost() - portSize.width; nscoord maxY = scrolledRect.YMost() - portSize.height; if (aX > maxX) aX = maxX; if (aY > maxY) aY = maxY; if (aX < scrolledRect.x) aX = scrolledRect.x; if (aY < scrolledRect.y) aY = scrolledRect.y; return NS_OK; } /* * this method wraps calls to ScrollToImpl(), either in one shot or incrementally, * based on the setting of the smooth scroll pref */ NS_IMETHODIMP nsScrollPortView::ScrollTo(nscoord aDestinationX, nscoord aDestinationY, PRUint32 aUpdateFlags) { mDestinationX = aDestinationX; mDestinationY = aDestinationY; ClampScrollValues(mDestinationX, mDestinationY, this); if (!(aUpdateFlags & (NS_VMREFRESH_DEFERRED | NS_VMREFRESH_SMOOTHSCROLL))) { // Asynchronous scrolling is not allowed, so we'll kill any existing // async-scrolling process and do an instant scroll delete mAsyncScroll; mAsyncScroll = nsnull; return ScrollToImpl(mDestinationX, mDestinationY); } PRInt32 currentVelocityX = 0; PRInt32 currentVelocityY = 0; PRBool isSmoothScroll = (aUpdateFlags & NS_VMREFRESH_SMOOTHSCROLL) && IsSmoothScrollingEnabled(); if (mAsyncScroll) { if (mAsyncScroll->mIsSmoothScroll) { currentVelocityX = mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2]; currentVelocityY = mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2 + 1]; } } else { mAsyncScroll = new AsyncScroll; if (mAsyncScroll) { mAsyncScroll->mScrollTimer = do_CreateInstance("@mozilla.org/timer;1"); if (!mAsyncScroll->mScrollTimer) { delete mAsyncScroll; mAsyncScroll = nsnull; } } if (!mAsyncScroll) { // some allocation failed. Scroll the normal way. return ScrollToImpl(mDestinationX, mDestinationY); } if (isSmoothScroll) { mAsyncScroll->mScrollTimer->InitWithFuncCallback( AsyncScrollCallback, this, SMOOTH_SCROLL_MSECS_PER_FRAME, nsITimer::TYPE_REPEATING_PRECISE); } else { mAsyncScroll->mScrollTimer->InitWithFuncCallback( AsyncScrollCallback, this, 0, nsITimer::TYPE_ONE_SHOT); } } mAsyncScroll->mFrameIndex = 0; mAsyncScroll->mIsSmoothScroll = isSmoothScroll; if (isSmoothScroll) { nsCOMPtr dev; mViewManager->GetDeviceContext(*getter_AddRefs(dev)); PRInt32 p2a = dev->AppUnitsPerDevPixel(); // compute velocity vectors ComputeVelocities(currentVelocityX, mOffsetX, mDestinationX, mAsyncScroll->mVelocities, p2a); ComputeVelocities(currentVelocityY, mOffsetY, mDestinationY, mAsyncScroll->mVelocities + 1, p2a); } return NS_OK; } static void AdjustChildWidgets(nsView *aView, nsPoint aWidgetToParentViewOrigin, PRInt32 aP2A, PRBool aInvalidate) { if (aView->HasWidget()) { nsIWidget* widget = aView->GetWidget(); nsWindowType type; widget->GetWindowType(type); if (type != eWindowType_popup) { nsRect bounds = aView->GetBounds(); nsPoint widgetOrigin = aWidgetToParentViewOrigin + nsPoint(bounds.x, bounds.y); widget->Move(NSAppUnitsToIntPixels(widgetOrigin.x, aP2A), NSAppUnitsToIntPixels(widgetOrigin.y, aP2A)); if (aInvalidate) { // Force the widget and everything in it to repaint. We can't // just use Invalidate because the widget might have child // widgets and they wouldn't get updated. We can't call // UpdateView(aView) because the area to be repainted might be // outside aView's clipped bounds. This isn't the greatest way // to achieve this, perhaps, but it works. widget->Show(PR_FALSE); widget->Show(PR_TRUE); } } // Don't recurse if the view has a widget, because we adjusted the view's // widget position, and its child widgets are relative to its position } else { nsPoint widgetToViewOrigin = aWidgetToParentViewOrigin + aView->GetPosition(); for (nsView* kid = aView->GetFirstChild(); kid; kid = kid->GetNextSibling()) { AdjustChildWidgets(kid, widgetToViewOrigin, aP2A, aInvalidate); } } } NS_IMETHODIMP nsScrollPortView::SetScrolledView(nsIView *aScrolledView) { NS_ASSERTION(GetFirstChild() == nsnull || GetFirstChild()->GetNextSibling() == nsnull, "Error scroll port has too many children"); // if there is already a child so remove it if (GetFirstChild() != nsnull) { mViewManager->RemoveChild(GetFirstChild()); } return mViewManager->InsertChild(this, aScrolledView, 0); } NS_IMETHODIMP nsScrollPortView::GetScrolledView(nsIView *&aScrolledView) const { aScrolledView = GetScrolledView(); return NS_OK; } NS_IMETHODIMP nsScrollPortView::GetScrollPosition(nscoord &aX, nscoord &aY) const { aX = mOffsetX; aY = mOffsetY; return NS_OK; } NS_IMETHODIMP nsScrollPortView::SetLineHeight(nscoord aHeight) { mLineHeight = aHeight; return NS_OK; } NS_IMETHODIMP nsScrollPortView::GetLineHeight(nscoord *aHeight) { *aHeight = mLineHeight; return NS_OK; } nsresult nsScrollPortView::CalcScrollOverflow(nscoord aX, nscoord aY, PRInt32& aPixelOverflowX, PRInt32& aPixelOverflowY) { // make sure the new position is in bounds nsView* scrolledView = GetScrolledView(); if (!scrolledView) return NS_ERROR_FAILURE; nsRect scrolledRect; scrolledView->GetDimensions(scrolledRect); nsSize portSize; this->GetDimensions(portSize); nscoord maxX = scrolledRect.XMost() - portSize.width; nscoord maxY = scrolledRect.YMost() - portSize.height; nsCOMPtr dev; mViewManager->GetDeviceContext(*getter_AddRefs(dev)); float p2a = (float)dev->AppUnitsPerDevPixel(); if (maxX != 0 && aX > maxX) aPixelOverflowX = NSAppUnitsToIntPixels(aX - maxX, p2a); if (maxY != 0 && aY > maxY) aPixelOverflowY = NSAppUnitsToIntPixels(aY - maxY, p2a); if (maxX != 0 && aX < scrolledRect.x) aPixelOverflowX = NSAppUnitsToIntPixels(scrolledRect.x - aX, p2a); if (maxY != 0 && aY < scrolledRect.y) aPixelOverflowY = NSAppUnitsToIntPixels(scrolledRect.y - aY, p2a); return NS_OK; } NS_IMETHODIMP nsScrollPortView::ScrollByLines(PRInt32 aNumLinesX, PRInt32 aNumLinesY, PRUint32 aUpdateFlags) { nscoord dx = mLineHeight*aNumLinesX; nscoord dy = mLineHeight*aNumLinesY; return ScrollTo(mDestinationX + dx, mDestinationY + dy, aUpdateFlags); } NS_IMETHODIMP nsScrollPortView::ScrollByLinesWithOverflow(PRInt32 aNumLinesX, PRInt32 aNumLinesY, PRInt32& aOverflowX, PRInt32& aOverflowY, PRUint32 aUpdateFlags) { nscoord dx = mLineHeight*aNumLinesX; nscoord dy = mLineHeight*aNumLinesY; CalcScrollOverflow(mDestinationX + dx, mDestinationY + dy, aOverflowX, aOverflowY); return ScrollTo(mDestinationX + dx, mDestinationY + dy, aUpdateFlags); } NS_IMETHODIMP nsScrollPortView::GetPageScrollDistances(nsSize *aDistances) { nsSize size; GetDimensions(size); // The page increment is the size of the page, minus the smaller of // 10% of the size or 2 lines. aDistances->width = size.width - PR_MIN(size.width / 10, 2 * mLineHeight); aDistances->height = size.height - PR_MIN(size.height / 10, 2 * mLineHeight); return NS_OK; } NS_IMETHODIMP nsScrollPortView::ScrollByPages(PRInt32 aNumPagesX, PRInt32 aNumPagesY, PRUint32 aUpdateFlags) { nsSize delta; GetPageScrollDistances(&delta); // put in the number of pages. delta.width *= aNumPagesX; delta.height *= aNumPagesY; return ScrollTo(mDestinationX + delta.width, mDestinationY + delta.height, aUpdateFlags); } NS_IMETHODIMP nsScrollPortView::ScrollByWhole(PRBool aTop, PRUint32 aUpdateFlags) { nscoord newPos = 0; if (!aTop) { nsSize scrolledSize; nsView* scrolledView = GetScrolledView(); scrolledView->GetDimensions(scrolledSize); newPos = scrolledSize.height; } ScrollTo(mDestinationX, newPos, aUpdateFlags); return NS_OK; } NS_IMETHODIMP nsScrollPortView::ScrollByPixels(PRInt32 aNumPixelsX, PRInt32 aNumPixelsY, PRInt32& aOverflowX, PRInt32& aOverflowY, PRUint32 aUpdateFlags) { nsCOMPtr dev; mViewManager->GetDeviceContext(*getter_AddRefs(dev)); PRInt32 p2a = dev->AppUnitsPerDevPixel(); nscoord dx = NSIntPixelsToAppUnits(aNumPixelsX, p2a); nscoord dy = NSIntPixelsToAppUnits(aNumPixelsY, p2a); CalcScrollOverflow(mDestinationX + dx, mDestinationY + dy, aOverflowX, aOverflowY); return ScrollTo(mDestinationX + dx, mDestinationY + dy, aUpdateFlags); } NS_IMETHODIMP nsScrollPortView::CanScroll(PRBool aHorizontal, PRBool aForward, PRBool &aResult) { nscoord offset = aHorizontal ? mOffsetX : mOffsetY; nsView* scrolledView = GetScrolledView(); if (!scrolledView) { aResult = PR_FALSE; return NS_ERROR_FAILURE; } nsRect scrolledRect; scrolledView->GetDimensions(scrolledRect); // Can scroll to Top or to Left? if (!aForward) { aResult = offset > (aHorizontal ? scrolledRect.x : scrolledRect.y); return NS_OK; } nsSize portSize; GetDimensions(portSize); nsCOMPtr dev; mViewManager->GetDeviceContext(*getter_AddRefs(dev)); PRInt32 p2a = dev->AppUnitsPerDevPixel(); nscoord max; if (aHorizontal) { max = scrolledRect.XMost() - portSize.width; // Round by pixel nscoord maxPx = NSAppUnitsToIntPixels(max, p2a); max = NSIntPixelsToAppUnits(maxPx, p2a); } else { max = scrolledRect.YMost() - portSize.height; // Round by pixel nscoord maxPx = NSAppUnitsToIntPixels(max, p2a); max = NSIntPixelsToAppUnits(maxPx, p2a); } // Can scroll to Bottom or to Right? aResult = (offset < max) ? PR_TRUE : PR_FALSE; return NS_OK; } /** * Given aBlitRegion in appunits, create and return an nsRegion in * device pixels that represents the device pixels whose centers are * contained in aBlitRegion. Whatever appunit area was removed from * aBlitRegion in that process is added to aRepaintRegion. An appunits * version of the result is placed in aAppunitsBlitRegion. * * We convert the blit region to pixels this way because in general * frames touch the pixels whose centers are contained in the * (possibly not pixel-aligned) frame bounds. */ static void ConvertBlitRegionToPixelRects(const nsRegion& aBlitRegion, nscoord aAppUnitsPerPixel, nsTArray* aPixelRects, nsRegion* aRepaintRegion, nsRegion* aAppunitsBlitRegion) { const nsRect* r; aPixelRects->Clear(); aAppunitsBlitRegion->SetEmpty(); // The rectangles in aBlitRegion don't overlap so converting them via // ToNearestPixels also produces a sequence of non-overlapping rectangles for (nsRegionRectIterator iter(aBlitRegion); (r = iter.Next());) { nsIntRect pixRect = r->ToNearestPixels(aAppUnitsPerPixel); aPixelRects->AppendElement(pixRect); aAppunitsBlitRegion->Or(*aAppunitsBlitRegion, pixRect.ToAppUnits(aAppUnitsPerPixel)); } nsRegion repaint; repaint.Sub(aBlitRegion, *aAppunitsBlitRegion); aRepaintRegion->Or(*aRepaintRegion, repaint); } /** * An nsTArray comparator that lets us sort nsIntRects by their right edge. */ class RightEdgeComparator { public: /** @return True if the elements are equals; false otherwise. */ PRBool Equals(const nsIntRect& aA, const nsIntRect& aB) const { return aA.XMost() == aB.XMost(); } /** @return True if (a < b); false otherwise. */ PRBool LessThan(const nsIntRect& aA, const nsIntRect& aB) const { return aA.XMost() < aB.XMost(); } }; // If aPixDelta has a negative component, flip aRect across the // axis in that direction. We do this so we can assume all scrolling is // down and to the right to simplify SortBlitRectsForCopy static nsIntRect FlipRect(const nsIntRect& aRect, nsIntPoint aPixDelta) { nsIntRect r = aRect; if (aPixDelta.x < 0) { r.x = -r.XMost(); } if (aPixDelta.y < 0) { r.y = -r.YMost(); } return r; } // Sort aRects so that moving rectangle aRects[i] - aPixDelta to aRects[i] // will not cause the rectangle to overlap any rectangles that haven't // moved yet. // See http://weblogs.mozillazine.org/roc/archives/2009/08/homework_answer.html static void SortBlitRectsForCopy(nsIntPoint aPixDelta, nsTArray* aRects) { nsTArray rects; for (PRUint32 i = 0; i < aRects->Length(); ++i) { nsIntRect* r = &aRects->ElementAt(i); nsIntRect rect = FlipRect(nsIntRect(r->x, r->y, r->width, r->height), aPixDelta); rects.AppendElement(rect); } rects.Sort(RightEdgeComparator()); aRects->Clear(); // This could probably be improved a bit for some worst-case scenarios. // But in common cases this should be very fast, and we shouldn't // make it more complex unless we really need to. while (!rects.IsEmpty()) { PRInt32 i = rects.Length() - 1; PRBool overlappedBelow; do { overlappedBelow = PR_FALSE; const nsIntRect& rectI = rects[i]; // see if any rectangle < i overlaps rectI horizontally and is below // rectI for (PRInt32 j = i - 1; j >= 0; --j) { if (rects[j].XMost() <= rectI.x) { // No rectangle with index <= j can overlap rectI horizontally break; } // Rectangle j overlaps rectI horizontally. if (rects[j].y >= rectI.y) { // Rectangle j is below rectangle i. This is the rightmost such // rectangle, so set i to this rectangle and continue. i = j; overlappedBelow = PR_TRUE; break; } } } while (overlappedBelow); // Rectangle i has no rectangles to the right or below. // Flip it back before saving the result. aRects->AppendElement(FlipRect(rects[i], aPixDelta)); rects.RemoveElementAt(i); } } void nsScrollPortView::Scroll(nsView *aScrolledView, nsPoint aTwipsDelta, nsIntPoint aPixDelta, PRInt32 aP2A, const nsTArray& aConfigurations) { if (aTwipsDelta.x != 0 || aTwipsDelta.y != 0) { /* If we should invalidate our wrapped view, we should do so at this * point. */ if (aScrolledView->NeedsInvalidateFrameOnScroll()) { mViewManager->GetViewObserver()->InvalidateFrameForScrolledView(aScrolledView); } nsPoint nearestWidgetOffset; nsIWidget *nearestWidget = GetNearestWidget(&nearestWidgetOffset); if (!nearestWidget || nearestWidget->GetTransparencyMode() == eTransparencyTransparent) { // Just update the view and adjust widgets // Recall that our widget's origin is at our bounds' top-left if (nearestWidget) { nearestWidget->ConfigureChildren(aConfigurations); } nsRect bounds(GetBounds()); nsPoint topLeft(bounds.x, bounds.y); AdjustChildWidgets(aScrolledView, GetPosition() - topLeft, aP2A, PR_FALSE); // We should call this after fixing up the widget positions to be // consistent with the view hierarchy. mViewManager->GetViewObserver()->InvalidateFrameForScrolledView(aScrolledView); } else { nsRegion blitRegion; nsRegion repaintRegion; mViewManager->GetRegionsForBlit(aScrolledView, aTwipsDelta, &blitRegion, &repaintRegion); blitRegion.MoveBy(nearestWidgetOffset); repaintRegion.MoveBy(nearestWidgetOffset); // We're going to bit-blit. Let the viewmanager know so it can // adjust dirty regions appropriately. mViewManager->WillBitBlit(this, aTwipsDelta); // innerPixRegion is in device pixels nsTArray blitRects; nsRegion blitRectsRegion; ConvertBlitRegionToPixelRects(blitRegion, aP2A, &blitRects, &repaintRegion, &blitRectsRegion); SortBlitRectsForCopy(aPixDelta, &blitRects); nearestWidget->Scroll(aPixDelta, blitRects, aConfigurations); AdjustChildWidgets(aScrolledView, nearestWidgetOffset, aP2A, PR_TRUE); repaintRegion.MoveBy(-nearestWidgetOffset); blitRectsRegion.MoveBy(-nearestWidgetOffset); mViewManager->UpdateViewAfterScroll(this, blitRectsRegion, repaintRegion); } } } NS_IMETHODIMP nsScrollPortView::ScrollToImpl(nscoord aX, nscoord aY) { PRInt32 dxPx = 0, dyPx = 0; // convert to pixels nsCOMPtr dev; mViewManager->GetDeviceContext(*getter_AddRefs(dev)); PRInt32 p2a = dev->AppUnitsPerDevPixel(); // Update the scrolled view's position nsresult rv = ClampScrollValues(aX, aY, this); if (NS_FAILED(rv)) { return rv; } PRInt32 xPixels = NSAppUnitsToIntPixels(aX, p2a); PRInt32 yPixels = NSAppUnitsToIntPixels(aY, p2a); aX = NSIntPixelsToAppUnits(xPixels, p2a); aY = NSIntPixelsToAppUnits(yPixels, p2a); // do nothing if we aren't scrolling. // this needs to be rechecked because of the clamping and // rounding if (aX == mOffsetX && aY == mOffsetY) { return NS_OK; } // figure out the diff by comparing old pos to new dxPx = NSAppUnitsToIntPixels(mOffsetX, p2a) - xPixels; dyPx = NSAppUnitsToIntPixels(mOffsetY, p2a) - yPixels; // notify the listeners. PRUint32 listenerCount; const nsIID& kScrollPositionListenerIID = NS_GET_IID(nsIScrollPositionListener); nsIScrollPositionListener* listener; if (nsnull != mListeners) { if (NS_SUCCEEDED(mListeners->Count(&listenerCount))) { for (PRUint32 i = 0; i < listenerCount; i++) { if (NS_SUCCEEDED(mListeners->QueryElementAt(i, kScrollPositionListenerIID, (void**)&listener))) { listener->ScrollPositionWillChange(this, aX, aY); NS_RELEASE(listener); } } } } nsView* scrolledView = GetScrolledView(); if (!scrolledView) return NS_ERROR_FAILURE; // move the scrolled view to the new location scrolledView->SetPositionIgnoringChildWidgets(-aX, -aY); // notify the listeners. nsTArray configurations; if (nsnull != mListeners) { if (NS_SUCCEEDED(mListeners->Count(&listenerCount))) { for (PRUint32 i = 0; i < listenerCount; i++) { if (NS_SUCCEEDED(mListeners->QueryElementAt(i, kScrollPositionListenerIID, (void**)&listener))) { listener->ViewPositionDidChange(this, &configurations); NS_RELEASE(listener); } } } } nsPoint twipsDelta(aX - mOffsetX, aY - mOffsetY); // store the new position mOffsetX = aX; mOffsetY = aY; Scroll(scrolledView, twipsDelta, nsIntPoint(dxPx, dyPx), p2a, configurations); mViewManager->SynthesizeMouseMove(PR_TRUE); // notify the listeners. if (nsnull != mListeners) { if (NS_SUCCEEDED(mListeners->Count(&listenerCount))) { for (PRUint32 i = 0; i < listenerCount; i++) { if (NS_SUCCEEDED(mListeners->QueryElementAt(i, kScrollPositionListenerIID, (void**)&listener))) { listener->ScrollPositionDidChange(this, aX, aY); NS_RELEASE(listener); } } } } return NS_OK; } PRBool nsScrollPortView::IsSmoothScrollingEnabled() { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { PRBool enabled; nsresult rv = prefs->GetBoolPref(SMOOTH_SCROLL_PREF_NAME, &enabled); if (NS_SUCCEEDED(rv)) { return enabled; } } return PR_FALSE; } /* * Callback function from timer used in nsScrollPortView::ScrollTo */ void nsScrollPortView::AsyncScrollCallback(nsITimer *aTimer, void* anInstance) { nsScrollPortView* self = static_cast(anInstance); if (self) { self->IncrementalScroll(); } } /* * manages data members and calls to ScrollTo from the (static) AsyncScrollCallback method */ void nsScrollPortView::IncrementalScroll() { if (!mAsyncScroll) return; nsWeakView thisView = this; if (mAsyncScroll->mIsSmoothScroll) { if (mAsyncScroll->mFrameIndex < SMOOTH_SCROLL_FRAMES) { ScrollToImpl(mOffsetX + mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2], mOffsetY + mAsyncScroll->mVelocities[mAsyncScroll->mFrameIndex*2 + 1]); if (!thisView.IsAlive()) return; mAsyncScroll->mFrameIndex++; return; } } else { ScrollToImpl(mDestinationX, mDestinationY); if (!thisView.IsAlive()) return; } delete mAsyncScroll; mAsyncScroll = nsnull; }