570 lines
14 KiB
C++
570 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Netscape 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/NPL/
|
|
*
|
|
* 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):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either 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 NPL, 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 NPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include "nsScrollPortView.h"
|
|
#include "nsIWidget.h"
|
|
#include "nsUnitConversion.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsIScrollbar.h"
|
|
#include "nsIDeviceContext.h"
|
|
#include "nsGUIEvent.h"
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsViewsCID.h"
|
|
#include "nsIScrollableView.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsILookAndFeel.h"
|
|
#include "nsISupportsArray.h"
|
|
#include "nsIScrollPositionListener.h"
|
|
#include "nsIRegion.h"
|
|
#include "nsViewManager.h"
|
|
|
|
static NS_DEFINE_IID(kWidgetCID, NS_CHILD_CID);
|
|
static NS_DEFINE_IID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
|
|
static NS_DEFINE_IID(kIClipViewIID, NS_ICLIPVIEW_IID);
|
|
|
|
|
|
nsScrollPortView::nsScrollPortView()
|
|
{
|
|
mOffsetX = mOffsetY = 0;
|
|
mOffsetXpx = mOffsetYpx = 0;
|
|
|
|
mListeners = 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (aIID.Equals(NS_GET_IID(nsIClipView))) {
|
|
*aInstancePtr = (void*)(nsIClipView*)this;
|
|
return NS_OK;
|
|
}
|
|
|
|
return nsView::QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
|
|
nsrefcnt nsScrollPortView::AddRef()
|
|
{
|
|
NS_WARNING("not supported for views");
|
|
return 1;
|
|
}
|
|
|
|
nsrefcnt nsScrollPortView::Release()
|
|
{
|
|
NS_WARNING("not supported for views");
|
|
return 1;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsScrollPortView::GetClipView(const nsIView** aClipView) const
|
|
{
|
|
*aClipView = this;
|
|
return NS_OK;
|
|
}
|
|
|
|
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::CreateScrollControls(nsNativeWidget aNative)
|
|
{
|
|
nsWidgetInitData initData;
|
|
initData.clipChildren = PR_TRUE;
|
|
initData.clipSiblings = PR_TRUE;
|
|
|
|
CreateWidget(kWidgetCID, &initData,
|
|
mWindow ? nsnull : aNative);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::SetWidget(nsIWidget *aWidget)
|
|
{
|
|
if (nsnull != aWidget) {
|
|
NS_ASSERTION(PR_FALSE, "please don't try and set a widget here");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::ComputeScrollOffsets(PRBool aAdjustWidgets)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::ShowQuality(PRBool aShow)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::GetShowQuality(PRBool &aShow) const
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::SetQuality(nsContentQuality aQuality)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::SetScrollPreference(nsScrollPreference aPref)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::GetScrollPreference(nsScrollPreference &aScrollPreference) const
|
|
{
|
|
return nsScrollPreference_kNeverScroll;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::ScrollTo(nscoord aX, nscoord aY, PRUint32 aUpdateFlags)
|
|
{
|
|
// do nothing if the we aren't scrolling.
|
|
if (aX == mOffsetX && aY == mOffsetY)
|
|
return NS_OK;
|
|
|
|
nsSize scrolledSize;
|
|
|
|
|
|
PRInt32 dxPx = 0, dyPx = 0;
|
|
|
|
// convert to pixels
|
|
nsIDeviceContext *dev;
|
|
float t2p;
|
|
float p2t;
|
|
|
|
mViewManager->GetDeviceContext(dev);
|
|
dev->GetAppUnitsToDevUnits(t2p);
|
|
dev->GetDevUnitsToAppUnits(p2t);
|
|
|
|
NS_RELEASE(dev);
|
|
|
|
// Update the scrolled view's position
|
|
|
|
// make sure the new position in in bounds
|
|
nsView* scrolledView = GetScrolledView();
|
|
|
|
#ifdef DEBUG_pollmann
|
|
NS_ASSERTION(scrolledView, "no scrolled view");
|
|
#endif
|
|
|
|
if (!scrolledView) return NS_ERROR_FAILURE;
|
|
scrolledView->GetDimensions(scrolledSize);
|
|
|
|
nsSize portSize;
|
|
GetDimensions(portSize);
|
|
|
|
nscoord maxX = scrolledSize.width - portSize.width;
|
|
nscoord maxY = scrolledSize.height - portSize.height;
|
|
|
|
if (aX > maxX)
|
|
aX = maxX;
|
|
|
|
if (aY > maxY)
|
|
aY = maxY;
|
|
|
|
if (aX < 0)
|
|
aX = 0;
|
|
|
|
if (aY < 0)
|
|
aY = 0;
|
|
|
|
// convert aX and aY in pixels
|
|
nscoord aXpx = NSTwipsToIntPixels(aX, t2p);
|
|
nscoord aYpx = NSTwipsToIntPixels(aY, t2p);
|
|
|
|
aX = NSIntPixelsToTwips(aXpx,p2t);
|
|
aY = NSIntPixelsToTwips(aYpx,p2t);
|
|
|
|
// do nothing if the 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 = mOffsetXpx - aXpx;
|
|
dyPx = mOffsetYpx - aYpx;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nsnull != scrolledView)
|
|
{
|
|
// move the scrolled view to the new location
|
|
scrolledView->SetPosition(-aX, -aY);
|
|
|
|
// store old position in pixels. We need to do this to make sure there is no
|
|
// round off errors. This could cause weird scrolling.
|
|
mOffsetXpx = aXpx;
|
|
mOffsetYpx = aYpx;
|
|
|
|
// store the new position
|
|
mOffsetX = aX;
|
|
mOffsetY = aY;
|
|
}
|
|
|
|
Scroll(scrolledView, dxPx, dyPx, t2p, 0);
|
|
|
|
// 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;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::SetControlInsets(const nsMargin &aInsets)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::GetControlInsets(nsMargin &aInsets) const
|
|
{
|
|
aInsets.left = aInsets.right = aInsets.top = aInsets.bottom = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::GetScrollbarVisibility(PRBool *aVerticalVisible,
|
|
PRBool *aHorizontalVisible) const
|
|
{
|
|
NS_WARNING("Attempt to get scrollbars visibility this is not xp!");
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsScrollPortView::AdjustChildWidgets(nsScrollPortView *aScrolling, nsView *aView, nscoord aDx, nscoord aDy, float scale)
|
|
{
|
|
|
|
nscoord offx, offy;
|
|
|
|
if (aScrolling == aView)
|
|
{
|
|
nsIWidget *widget;
|
|
aScrolling->GetOffsetFromWidget(&aDx, &aDy, widget);
|
|
NS_IF_RELEASE(widget);
|
|
}
|
|
|
|
aView->GetPosition(&offx, &offy);
|
|
|
|
aDx += offx;
|
|
aDy += offy;
|
|
|
|
nsView *kid;
|
|
for (kid = aView->GetFirstChild(); kid != nsnull; kid = kid->GetNextSibling())
|
|
{
|
|
nsIWidget *win;
|
|
kid->GetWidget(win);
|
|
|
|
if (nsnull != win)
|
|
{
|
|
nsRect bounds;
|
|
|
|
#if 0
|
|
win->BeginResizingChildren();
|
|
#endif
|
|
kid->GetBounds(bounds);
|
|
|
|
win->Move(NSTwipsToIntPixels((bounds.x + aDx), scale), NSTwipsToIntPixels((bounds.y + aDy), scale));
|
|
}
|
|
|
|
// 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 positon
|
|
if (nsnull == win)
|
|
AdjustChildWidgets(aScrolling, kid, aDx, aDy, scale);
|
|
|
|
if (nsnull != win)
|
|
{
|
|
NS_RELEASE(win);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsScrollPortView::SetScrolledView(nsIView *aScrolledView)
|
|
{
|
|
PRInt32 count = GetChildCount();
|
|
|
|
NS_ASSERTION(count <= 1,"Error scroll port has too many children");
|
|
|
|
// if there is already a child so remove it
|
|
if (count == 1)
|
|
{
|
|
nsView* child = GetFirstChild();
|
|
mViewManager->RemoveChild(child);
|
|
}
|
|
|
|
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::SetScrollProperties(PRUint32 aProperties)
|
|
{
|
|
mScrollProperties = aProperties;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::GetScrollProperties(PRUint32 *aProperties)
|
|
{
|
|
*aProperties = mScrollProperties;
|
|
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;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::ScrollByLines(PRInt32 aNumLinesX, PRInt32 aNumLinesY)
|
|
{
|
|
nscoord dx = mLineHeight*aNumLinesX;
|
|
nscoord dy = mLineHeight*aNumLinesY;
|
|
|
|
ScrollTo(mOffsetX + dx, mOffsetY + dy, 0);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::ScrollByPages(PRInt32 aNumPages)
|
|
{
|
|
nsSize size;
|
|
GetDimensions(size);
|
|
|
|
// scroll % of the window
|
|
nscoord dy = nscoord(float(size.height)*PAGE_SCROLL_PERCENT);
|
|
|
|
// put in the number of pages.
|
|
dy *= aNumPages;
|
|
|
|
ScrollTo(mOffsetX, mOffsetY + dy, 0);
|
|
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::ScrollByWhole(PRBool aTop)
|
|
{
|
|
nscoord newPos = 0;
|
|
|
|
if (!aTop) {
|
|
nsSize scrolledSize;
|
|
nsView* scrolledView = GetScrolledView();
|
|
scrolledView->GetDimensions(scrolledSize);
|
|
newPos = scrolledSize.height;
|
|
}
|
|
|
|
ScrollTo(0, newPos, 0);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
PRBool nsScrollPortView::CannotBitBlt(nsView* aScrolledView)
|
|
{
|
|
PRUint32 scrolledViewFlags = aScrolledView->GetViewFlags();
|
|
|
|
return (mScrollProperties & NS_SCROLL_PROPERTY_NEVER_BLIT) ||
|
|
(scrolledViewFlags & NS_VIEW_FLAG_DONT_BITBLT) ||
|
|
(!(mScrollProperties & NS_SCROLL_PROPERTY_ALWAYS_BLIT)
|
|
&& !mViewManager->CanScrollWithBitBlt(aScrolledView));
|
|
}
|
|
|
|
|
|
void nsScrollPortView::Scroll(nsView *aScrolledView, PRInt32 aDx, PRInt32 aDy, float scale, PRUint32 aUpdateFlags)
|
|
{
|
|
if ((aDx != 0) || (aDy != 0))
|
|
{
|
|
// Since we keep track of the dirty region as absolute screen coordintes,
|
|
// we need to offset it by the amount we scrolled.
|
|
nsCOMPtr<nsIRegion> dirtyRegion;
|
|
GetDirtyRegion(*getter_AddRefs(dirtyRegion));
|
|
dirtyRegion->Offset(aDx, aDy);
|
|
|
|
nsIWidget *scrollWidget = nsnull;
|
|
|
|
GetWidget(scrollWidget);
|
|
|
|
if (nsnull == scrollWidget)
|
|
{
|
|
// if we don't have a scroll widget then we must just update.
|
|
mViewManager->UpdateView(this, 0);
|
|
|
|
} else if (CannotBitBlt(aScrolledView)) {
|
|
// we can't blit for some reason just update the view and adjust any heavy weight widgets
|
|
mViewManager->UpdateView(this, 0);
|
|
AdjustChildWidgets(this, aScrolledView, 0, 0, scale);
|
|
} else { // if we can blit and have a scrollwidget then scroll.
|
|
// Scroll the contents of the widget by the specfied amount, and scroll
|
|
// the child widgets
|
|
scrollWidget->Scroll(aDx, aDy, nsnull);
|
|
mViewManager->UpdateViewAfterScroll(this, aDx, aDy);
|
|
}
|
|
|
|
NS_IF_RELEASE(scrollWidget);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::Paint(nsIRenderingContext& rc, const nsRect& rect,
|
|
PRUint32 aPaintFlags, PRBool &aResult)
|
|
{
|
|
PRBool clipEmpty;
|
|
rc.PushState();
|
|
nsRect bounds;
|
|
GetDimensions(bounds);
|
|
bounds.x = bounds.y = 0;
|
|
rc.SetClipRect(bounds, nsClipCombine_kIntersect, clipEmpty);
|
|
|
|
nsresult rv = nsView::Paint(rc, rect, aPaintFlags, aResult);
|
|
|
|
rc.PopState(clipEmpty);
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsScrollPortView::Paint(nsIRenderingContext& aRC, const nsIRegion& aRegion,
|
|
PRUint32 aPaintFlags, PRBool &aResult)
|
|
{
|
|
PRBool clipEmpty;
|
|
aRC.PushState();
|
|
nsRect bounds;
|
|
GetDimensions(bounds);
|
|
bounds.x = bounds.y = 0;
|
|
aRC.SetClipRect(bounds, nsClipCombine_kIntersect, clipEmpty);
|
|
|
|
nsresult rv = nsView::Paint(aRC, aRegion, aPaintFlags, aResult);
|
|
|
|
aRC.PopState(clipEmpty);
|
|
|
|
return rv;
|
|
}
|