/* -*- 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 "nsView.h"
#include "nsIWidget.h"
#include "nsViewManager.h"
#include "nsIFrame.h"
#include "nsIPresContext.h"
#include "nsIWidget.h"
#include "nsIButton.h"
#include "nsGUIEvent.h"
#include "nsIDeviceContext.h"
#include "nsIComponentManager.h"
#include "nsIRenderingContext.h"
#include "nsTransform2D.h"
#include "nsIScrollableView.h"
#include "nsVoidArray.h"
#include "nsGfxCIID.h"
#include "nsIRegion.h"


//mmptemp

static nsEventStatus PR_CALLBACK HandleEvent(nsGUIEvent *aEvent);


//#define SHOW_VIEW_BORDERS
//#define HIDE_ALL_WIDGETS

//
// Main events handler
//
nsEventStatus PR_CALLBACK HandleEvent(nsGUIEvent *aEvent)
{ 
//printf(" %d %d %d (%d,%d) \n", aEvent->widget, aEvent->widgetSupports, 
//       aEvent->message, aEvent->point.x, aEvent->point.y);
  nsEventStatus result = nsEventStatus_eIgnore;
  nsView       *view = nsView::GetViewFor(aEvent->widget);

  if (view)
  {
    view->GetViewManager()->DispatchEvent(aEvent, &result);
  }

  return result;
}

MOZ_DECL_CTOR_COUNTER(nsView)

nsView::nsView()
{
  MOZ_COUNT_CTOR(nsView);

  mVis = nsViewVisibility_kShow;
  // Views should be transparent by default. Not being transparent is
  // a promise that the view will paint all its pixels opaquely. Views
  // should make this promise explicitly by calling
  // SetViewContentTransparency.
  mVFlags = NS_VIEW_FLAG_TRANSPARENT;
  mOpacity = 1.0f;
  mViewManager = nsnull;
  mChildRemoved = PR_FALSE;
}

void nsView::DropMouseGrabbing() {
  // check to see if we are grabbing events
  if (mViewManager->GetMouseEventGrabber() == this) {
    // we are grabbing events. Move the grab to the parent if we can.
    PRBool boolResult; //not used
    // if GetParent() returns null, then we release the grab, which is the best we can do
    mViewManager->GrabMouseEvents(GetParent(), boolResult);
  }
}

nsView::~nsView()
{
  MOZ_COUNT_DTOR(nsView);

  while (GetFirstChild())
  {
    nsView* child = GetFirstChild();
    if (child->GetViewManager() == mViewManager) {
      child->Destroy();
    } else {
      // just unhook it. Someone else will want to destroy this.
      RemoveChild(child);
    }
  }

  DropMouseGrabbing();
  
  if (mViewManager)
  {
    nsView *rootView = mViewManager->GetRootView();
    
    if (rootView)
    {
      // Root views can have parents!
      if (mParent)
      {
        mViewManager->RemoveChild(this);
      }

      if (rootView == this)
      {
        // Inform the view manager that the root view has gone away...
        mViewManager->SetRootView(nsnull);
      }
    }
    else if (mParent)
    {
      mParent->RemoveChild(this);
    }
    
    mViewManager = nsnull;
  }
  else if (mParent)
  {
    mParent->RemoveChild(this);
  }

  if (mZParent)
  {
    mZParent->RemoveReparentedView();
    mZParent->Destroy();
  }

  // Destroy and release the widget
  if (mWindow)
  {
    mWindow->SetClientData(nsnull);
    mWindow->Destroy();
    NS_RELEASE(mWindow);
  }
  NS_IF_RELEASE(mDirtyRegion);

  delete mClipRect;
}

nsresult nsView::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
  if (nsnull == aInstancePtr) {
    return NS_ERROR_NULL_POINTER;
  }
  
  *aInstancePtr = nsnull;
  
  if (aIID.Equals(NS_GET_IID(nsIView)) || (aIID.Equals(NS_GET_IID(nsISupports)))) {
    *aInstancePtr = (void*)(nsIView*)this;
    return NS_OK;
  }

  return NS_NOINTERFACE;
}

nsrefcnt nsView::AddRef() 
{
  NS_WARNING("not supported for views");
  return 1;
}

nsrefcnt nsView::Release()
{
  NS_WARNING("not supported for views");
  return 1;
}

nsView* nsView::GetViewFor(nsIWidget* aWidget)
{           
  NS_PRECONDITION(nsnull != aWidget, "null widget ptr");
	
  // The widget's client data points back to the owning view
  if (aWidget) {
    void* clientData;
    aWidget->GetClientData(clientData);
    nsISupports* data = (nsISupports*)clientData;
    
    if (data) {
      nsIView* view = nsnull;
      if (NS_SUCCEEDED(data->QueryInterface(NS_GET_IID(nsIView), (void **)&view))) {
        return NS_STATIC_CAST(nsView*, view);
      }
    }
  }
  return nsnull;
}

nsresult nsIView::Init(nsIViewManager* aManager,
                       const nsRect &aBounds,
                       const nsIView *aParent,
                       nsViewVisibility aVisibilityFlag)
{
  //printf(" \n callback=%d data=%d", aWidgetCreateCallback, aCallbackData);
  NS_PRECONDITION(nsnull != aManager, "null ptr");
  if (nsnull == aManager) {
    return NS_ERROR_NULL_POINTER;
  }
  if (nsnull != mViewManager) {
    return NS_ERROR_ALREADY_INITIALIZED;
  }
  // we don't hold a reference to the view manager
  mViewManager = NS_STATIC_CAST(nsViewManager*, aManager);

  nsView* v = NS_STATIC_CAST(nsView*, this);
  v->SetPosition(aBounds.x, aBounds.y);
  nsRect dim(0, 0, aBounds.width, aBounds.height);
  v->SetDimensions(dim, PR_FALSE);
  v->SetVisibility(aVisibilityFlag);

  // We shouldn't set the parent here. It should be set when we put this view
  // into the view hierarchy.
  v->SetParent(NS_STATIC_CAST(nsView*, NS_CONST_CAST(nsIView*, aParent)));
  return NS_OK;
}

void nsIView::Destroy()
{
  delete this;
}

NS_IMETHODIMP nsView::Paint(nsIRenderingContext& rc, const nsRect& rect,
                              PRUint32 aPaintFlags, PRBool &aResult)
{
    // Just paint, assume compositor knows what it's doing.
    if (nsnull != mClientData) {
      nsCOMPtr<nsIViewObserver> observer;
      if (NS_OK == mViewManager->GetViewObserver(*getter_AddRefs(observer))) {
        observer->Paint((nsIView *)this, rc, rect);
      }
    }
        return NS_OK;
}

NS_IMETHODIMP nsView::Paint(nsIRenderingContext& rc, const nsIRegion& region,
                              PRUint32 aPaintFlags, PRBool &aResult)
{
  // XXX apply region to rc
  // XXX get bounding rect from region
  //if (nsnull != mClientData)
  //{
  //  nsIViewObserver *obs;
  //
  //  if (NS_OK == mViewManager->GetViewObserver(obs))
  //  {
  //    obs->Paint((nsIView *)this, rc, rect, aPaintFlags);
  //    NS_RELEASE(obs);
  //  }
  //}
  aResult = PR_FALSE;
  return NS_ERROR_NOT_IMPLEMENTED;
}

void nsView::SetPosition(nscoord aX, nscoord aY)
{
  mDimBounds.x += aX - mPosX;
  mDimBounds.y += aY - mPosY;
  mPosX = aX;
  mPosY = aY;

  NS_ASSERTION(GetParent() || (aX == 0 && aY == 0),
               "Don't try to move the root widget to something non-zero");

  ResetWidgetPosition(PR_TRUE);
}

void nsView::SetPositionIgnoringChildWidgets(nscoord aX, nscoord aY)
{
  mDimBounds.x += aX - mPosX;
  mDimBounds.y += aY - mPosY;
  mPosX = aX;
  mPosY = aY;

  ResetWidgetPosition(PR_FALSE);
}

void nsView::ResetWidgetPosition(PRBool aRecurse) {
  if (mWindow)
  {
    // see if we are caching our widget changes. Yes? 
    // mark us as changed. Later we will actually move the 
    // widget.
    PRBool caching = PR_FALSE;
    mViewManager->IsCachingWidgetChanges(&caching);
    if (caching) {
      mVFlags |= NS_VIEW_FLAG_WIDGET_MOVED;
      return;
    }

    nsIDeviceContext  *dx;
    float             scale;
  
    mViewManager->GetDeviceContext(dx);
    scale = dx->AppUnitsToDevUnits();
    NS_RELEASE(dx);

    nsPoint offset(0, 0);
    if (GetParent()) {
      GetParent()->GetNearestWidget(&offset);
    }

    mWindow->Move(NSTwipsToIntPixels((mDimBounds.x + offset.x), scale),
                  NSTwipsToIntPixels((mDimBounds.y + offset.y), scale));
  } else if (aRecurse) {
    // reposition any widgets under this view
    for (nsView* v = GetFirstChild(); v; v = v->GetNextSibling()) {
      v->ResetWidgetPosition(aRecurse);
    }
  }
}

NS_IMETHODIMP nsView::SynchWidgetSizePosition()
{
  // if the widget was moved or resized
  if (mVFlags & NS_VIEW_FLAG_WIDGET_MOVED || mVFlags & NS_VIEW_FLAG_WIDGET_RESIZED)
  {
    nsIDeviceContext  *dx;
    float             t2p;

    mViewManager->GetDeviceContext(dx);
    t2p = dx->AppUnitsToDevUnits();
    NS_RELEASE(dx);
    // if we just resized do it
    if (mVFlags & NS_VIEW_FLAG_WIDGET_RESIZED) 
    {

      PRInt32 width = NSTwipsToIntPixels(mDimBounds.width, t2p);
      PRInt32 height = NSTwipsToIntPixels(mDimBounds.height, t2p);

      nsRect bounds;
      mWindow->GetBounds(bounds);

      if (bounds.width != width || bounds.height != bounds.height) {
        mWindow->Resize(width,height, PR_TRUE);
      }

      mVFlags &= ~NS_VIEW_FLAG_WIDGET_RESIZED;
    } 
    
    if (mVFlags & NS_VIEW_FLAG_WIDGET_MOVED) {
      // if we just moved do it.
      nsPoint offset;
      GetParent()->GetNearestWidget(&offset);

      PRInt32 x = NSTwipsToIntPixels(mDimBounds.x + offset.x, t2p);
      PRInt32 y = NSTwipsToIntPixels(mDimBounds.y + offset.y, t2p);

      nsRect bounds;
      mWindow->GetBounds(bounds);
      
      if (bounds.x != x || bounds.y != y) {
         mWindow->Move(x,y);
      }

      mVFlags &= ~NS_VIEW_FLAG_WIDGET_MOVED;
    }        
  }
  

  return NS_OK;
}

void nsView::SetDimensions(const nsRect& aRect, PRBool aPaint)
{
  nsRect dims = aRect;
  dims.MoveBy(mPosX, mPosY);

  if (mDimBounds.x == dims.x && mDimBounds.y == dims.y && mDimBounds.width == dims.width
      && mDimBounds.height == dims.height) {
    return;
  }
  
  if (nsnull == mWindow)
  {
    mDimBounds = dims;
  }
  else
  {
    PRBool needToMoveWidget = mDimBounds.x != dims.x || mDimBounds.y != dims.y;

    mDimBounds = dims;

    PRBool caching = PR_FALSE;
    mViewManager->IsCachingWidgetChanges(&caching);
    if (caching) {
      mVFlags |= NS_VIEW_FLAG_WIDGET_RESIZED | (needToMoveWidget ? NS_VIEW_FLAG_WIDGET_MOVED : 0);
      return;
    }

    nsIDeviceContext  *dx;
    float             t2p;
  
    mViewManager->GetDeviceContext(dx);
    t2p = dx->AppUnitsToDevUnits();

    if (needToMoveWidget) {
      NS_ASSERTION(GetParent(), "Don't try to move the root widget, dude");

      nsPoint offset;
      GetParent()->GetNearestWidget(&offset);
    
      mWindow->Move(NSTwipsToIntPixels((mDimBounds.x + offset.x), t2p),
                    NSTwipsToIntPixels((mDimBounds.y + offset.y), t2p));
    }
    mWindow->Resize(NSTwipsToIntPixels(mDimBounds.width, t2p), NSTwipsToIntPixels(mDimBounds.height, t2p),
                    aPaint);

    NS_RELEASE(dx);
  }
}

NS_IMETHODIMP nsView::SetVisibility(nsViewVisibility aVisibility)
{

  mVis = aVisibility;

  if (aVisibility == nsViewVisibility_kHide)
  {
    DropMouseGrabbing();
  }

  if (nsnull != mWindow)
  {
#ifndef HIDE_ALL_WIDGETS
    if (mVis == nsViewVisibility_kShow)
      mWindow->Show(PR_TRUE);
    else
#endif
      mWindow->Show(PR_FALSE);
  }

  return NS_OK;
}

NS_IMETHODIMP nsView::SetFloating(PRBool aFloatingView)
{
	if (aFloatingView)
		mVFlags |= NS_VIEW_FLAG_FLOATING;
	else
		mVFlags &= ~NS_VIEW_FLAG_FLOATING;

#if 0
	// recursively make all sub-views "floating" grr.
	for (nsView* child = mFirstChild; chlid; child = child->GetNextSibling()) {
		child->SetFloating(aFloatingView);
	}
#endif

	return NS_OK;
}

void nsView::InsertChild(nsView *aChild, nsView *aSibling)
{
  NS_PRECONDITION(nsnull != aChild, "null ptr");

  if (nsnull != aChild)
  {
    if (nsnull != aSibling)
    {
#ifdef NS_DEBUG
      NS_ASSERTION(aSibling->GetParent() == this, "tried to insert view with invalid sibling");
#endif
      //insert after sibling
      aChild->SetNextSibling(aSibling->GetNextSibling());
      aSibling->SetNextSibling(aChild);
    }
    else
    {
      aChild->SetNextSibling(mFirstChild);
      mFirstChild = aChild;
    }
    aChild->SetParent(this);
  }
}

void nsView::RemoveChild(nsView *child)
{
  NS_PRECONDITION(nsnull != child, "null ptr");

  if (nsnull != child)
  {
    nsView* prevKid = nsnull;
    nsView* kid = mFirstChild;
    PRBool found = PR_FALSE;
    while (nsnull != kid) {
      if (kid == child) {
        if (nsnull != prevKid) {
          prevKid->SetNextSibling(kid->GetNextSibling());
        } else {
          mFirstChild = kid->GetNextSibling();
        }
        child->SetParent(nsnull);
        found = PR_TRUE;
        break;
      }
      prevKid = kid;
      mChildRemoved = PR_TRUE;
	    kid = kid->GetNextSibling();
    }
    NS_ASSERTION(found, "tried to remove non child");
  }
}

NS_IMETHODIMP nsView::SetOpacity(float opacity)
{
  mOpacity = opacity;
  return NS_OK;
}

NS_IMETHODIMP nsView::SetContentTransparency(PRBool aTransparent)
{
  if (aTransparent == PR_TRUE)
    mVFlags |= NS_VIEW_FLAG_TRANSPARENT;
  else
    mVFlags &= ~NS_VIEW_FLAG_TRANSPARENT;

  return NS_OK;
}

nsresult nsIView::CreateWidget(const nsIID &aWindowIID,
                                   nsWidgetInitData *aWidgetInitData,
                                   nsNativeWidget aNative,
                                   PRBool aEnableDragDrop,
                                   PRBool aResetVisibility,
                                   nsContentType aContentType)
{
  nsIDeviceContext  *dx;
  nsRect            trect = mDimBounds;
  float             scale;

  NS_IF_RELEASE(mWindow);

  mViewManager->GetDeviceContext(dx);
  scale = dx->AppUnitsToDevUnits();

  trect *= scale;

  nsView* v = NS_STATIC_CAST(nsView*, this);
  if (NS_OK == v->LoadWidget(aWindowIID))
  {
    PRBool usewidgets;

    dx->SupportsNativeWidgets(usewidgets);

    if (PR_TRUE == usewidgets)
    {
      PRBool initDataPassedIn = PR_TRUE;
      nsWidgetInitData initData;
      if (!aWidgetInitData) {
        // No initData, we're a child window
        // Create initData to pass in params
        initDataPassedIn = PR_FALSE;
        initData.clipChildren = PR_TRUE; // Clip child window's children
        aWidgetInitData = &initData;
      }
      aWidgetInitData->mContentType = aContentType;

      if (aNative)
        mWindow->Create(aNative, trect, ::HandleEvent, dx, nsnull, nsnull, aWidgetInitData);
      else
      {
        if (!initDataPassedIn && GetParent() && 
          GetParent()->GetViewManager() != mViewManager)
          initData.mListenForResizes = PR_TRUE;

        nsIWidget* parentWidget = GetParent() ? GetParent()->GetNearestWidget(nsnull)
          : nsnull;
        mWindow->Create(parentWidget, trect,
                        ::HandleEvent, dx, nsnull, nsnull, aWidgetInitData);
      }
      if (aEnableDragDrop) {
        mWindow->EnableDragDrop(PR_TRUE);
      }
      
      // propagate the z-index to the widget.
      mWindow->SetZIndex(mZIndex);
    }
  }

  //make sure visibility state is accurate
  
  if (aResetVisibility) {
    v->SetVisibility(GetVisibility());
  }

  NS_RELEASE(dx);

  return NS_OK;
}

void nsView::SetZIndex(PRBool aAuto, PRInt32 aZIndex, PRBool aTopMost)
{
  mVFlags = (mVFlags & ~NS_VIEW_FLAG_AUTO_ZINDEX) | (aAuto ? NS_VIEW_FLAG_AUTO_ZINDEX : 0);
  mZIndex = aZIndex;
  SetTopMost(aTopMost);
  
  if (nsnull != mWindow) {
    mWindow->SetZIndex(aZIndex);
  }
}

NS_IMETHODIMP nsView::SetWidget(nsIWidget *aWidget)
{
  NS_IF_RELEASE(mWindow);
  mWindow = aWidget;

  if (nsnull != mWindow)
  {
    NS_ADDREF(mWindow);
    mWindow->SetClientData((void *)this);
  }

  return NS_OK;
}

//
// internal window creation functions
//
nsresult nsView::LoadWidget(const nsCID &aClassIID)
{
  nsresult rv = nsComponentManager::CreateInstance(aClassIID, nsnull, NS_GET_IID(nsIWidget), (void**)&mWindow);

  if (NS_OK == rv) {
    // Set the widget's client data
    mWindow->SetClientData((void*)this);
  }

  return rv;
}

#ifdef DEBUG
void nsIView::List(FILE* out, PRInt32 aIndent) const
{
  PRInt32 i;
  for (i = aIndent; --i >= 0; ) fputs("  ", out);
  fprintf(out, "%p ", (void*)this);
  if (nsnull != mWindow) {
    nsRect windowBounds;
    nsRect nonclientBounds;
    float p2t;
    nsIDeviceContext *dx;
    mViewManager->GetDeviceContext(dx);
    p2t = dx->DevUnitsToAppUnits();
    NS_RELEASE(dx);
    mWindow->GetClientBounds(windowBounds);
    windowBounds *= p2t;
    mWindow->GetBounds(nonclientBounds);
    nonclientBounds *= p2t;
    nsrefcnt widgetRefCnt = mWindow->AddRef() - 1;
    mWindow->Release();
    fprintf(out, "(widget=%p[%d] pos={%d,%d,%d,%d}) ",
            (void*)mWindow, widgetRefCnt,
            nonclientBounds.x, nonclientBounds.y,
            windowBounds.width, windowBounds.height);
  }
  nsRect brect = GetBounds();
  fprintf(out, "{%d,%d,%d,%d}",
          brect.x, brect.y, brect.width, brect.height);
  const nsView* v = NS_STATIC_CAST(const nsView*, this);
  if (v->IsZPlaceholderView()) {
    fprintf(out, " z-placeholder(%p)",
            (void*)NS_STATIC_CAST(const nsZPlaceholderView*, this)->GetReparentedView());
  }
  if (v->GetZParent()) {
    fprintf(out, " zparent=%p", (void*)v->GetZParent());
  }
  fprintf(out, " z=%d vis=%d opc=%1.3f tran=%d clientData=%p <\n",
          mZIndex, mVis, mOpacity, IsTransparent(), mClientData);
  for (nsView* kid = mFirstChild; kid; kid = kid->GetNextSibling()) {
    NS_ASSERTION(kid->GetParent() == this, "incorrect parent");
    kid->List(out, aIndent + 1);
  }
  for (i = aIndent; --i >= 0; ) fputs("  ", out);
  fputs(">\n", out);
}
#endif // DEBUG

nsIWidget* nsIView::GetNearestWidget(nsPoint* aOffset)
{
  nsPoint pt(0, 0);
  nsView* v;
  for (v = NS_STATIC_CAST(nsView*, this);
       v && !v->HasWidget(); v = v->GetParent()) {
    pt += v->GetPosition();
  }
  if (!v) {
    if (aOffset) {
      *aOffset = pt;
    }
    return NS_STATIC_CAST(nsView*, this)->GetViewManager()->GetWidget();
  }

  // pt is now the offset from v's origin to this's origin
  // The widget's origin is the top left corner of v's bounds, which may
  // not coincide with v's origin
  if (aOffset) {
    nsRect vBounds = v->GetBounds();
    *aOffset = pt + v->GetPosition() -  nsPoint(vBounds.x, vBounds.y);
  }
  return v->GetWidget();
}

nsresult nsView::GetDirtyRegion(nsIRegion*& aRegion)
{
  if (nsnull == mDirtyRegion) {
    nsresult rv = GetViewManager()->CreateRegion(&mDirtyRegion);
    if (NS_FAILED(rv))
      return rv;
  }

  aRegion = mDirtyRegion;
  NS_ADDREF(aRegion);
  return NS_OK;
}

PRBool nsIView::IsRoot() const
{
  NS_ASSERTION(mViewManager != nsnull," View manager is null in nsView::IsRoot()");
  return mViewManager->GetRootView() == this;
}

PRBool nsIView::ExternalIsRoot() const
{
  return nsIView::IsRoot();
}

// Returns PR_TRUE iff we found a placeholder parent edge on the way
static PRBool ApplyClipRect(const nsView* aView, nsRect* aRect, PRBool aFollowPlaceholders)
{
  // this records the offset from the origin of the current aView
  // to the origin of the initial aView
  nsPoint offset(0, 0);
  PRBool lastViewIsFloating = aView->GetFloating();
  PRBool foundPlaceholders = PR_FALSE;
  while (PR_TRUE) {
    const nsView* parentView = aView->GetParent();
    nsPoint offsetFromParent = aView->GetPosition();

    const nsView* zParent = aView->GetZParent();
    if (zParent) {
      foundPlaceholders = PR_TRUE;
      if (aFollowPlaceholders) {
        // correct offsetFromParent to account for the fact that we're
        // switching parentView to ZParent
        // Note tha the common case is that parentView is an ancestor of
        // ZParent.
        const nsView* zParentChain;
        for (zParentChain = zParent;
             zParentChain != parentView && zParentChain;
             zParentChain = zParentChain->GetParent()) {
          offsetFromParent -= zParentChain->GetPosition();
        }
        if (!zParentChain) {
          // uh oh. As we walked up the tree, we missed zParent. This can
          // happen because of odd (but legal) containing block hierarchies.
          // Just compute the required offset from scratch; this is slow,
          // but hopefully rarely executed.
          offsetFromParent = nsViewManager::ComputeViewOffset(aView)
            - nsViewManager::ComputeViewOffset(zParent);
        }
        parentView = zParent;
      }
    }

    if (!parentView) {
      break;
    }

    PRBool parentIsFloating = parentView->GetFloating();
    if (lastViewIsFloating && !parentIsFloating) {
      break;
    }

    // now make offset be the offset from parentView's origin to the initial
    // aView's origin
    offset += offsetFromParent;
    if (parentView->GetClipChildrenToBounds(aFollowPlaceholders)) {
      // Get the parent's clip rect (which is just the dimensions in this
      // case) into the initial aView's coordinates
      nsRect clipRect = parentView->GetDimensions();
      clipRect -= offset;
      PRBool overlap = aRect->IntersectRect(clipRect, *aRect);
      if (!overlap) {
        break;
      }
    }
    const nsRect* r = parentView->GetClipChildrenToRect();
    if (r && !aFollowPlaceholders) {
      // Get the parent's clip rect into the initial aView's coordinates
      nsRect clipRect = *r;
      clipRect -= offset;
      PRBool overlap = aRect->IntersectRect(clipRect, *aRect);
      if (!overlap) {
        break;
      }
    }

    aView = parentView;
    lastViewIsFloating = parentIsFloating;
  }

  return foundPlaceholders;
}

nsRect nsView::GetClippedRect()
{
  nsRect clip = GetDimensions();
  PRBool foundPlaceholders = ApplyClipRect(this, &clip, PR_FALSE);
  if (foundPlaceholders && !clip.IsEmpty()) {
    ApplyClipRect(this, &clip, PR_TRUE);
  }
  return clip;
}
