/* -*- 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 Communicator client 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):
 * Original Author: David W. Hyatt (hyatt@netscape.com)
 *  Mike Pinkerton (pinkerton@netscape.com)
 *
 * 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 "nsCOMPtr.h"
#include "nsXULTreeOuterGroupFrame.h"
#include "nsXULAtoms.h"
#include "nsHTMLAtoms.h"
#include "nsIContent.h"
#include "nsINameSpaceManager.h"
#include "nsIScrollableFrame.h"
#include "nsIScrollbarFrame.h"
#include "nsISupportsArray.h"
#include "nsCSSFrameConstructor.h"
#include "nsIDocument.h"
#include "nsTreeItemDragCapturer.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMMouseEvent.h"
#include "nsIDragService.h"
#include "nsIServiceManager.h"
#include "nsIScrollableView.h"
#include "nsTreeLayout.h"
#include "nsITimer.h"
#include "nsIBindingManager.h"
#include "nsScrollPortFrame.h"
#include "nsIRenderingContext.h"
#include "nsIDeviceContext.h"
#include "nsIFontMetrics.h"
#include "nsIStyleContext.h"
#include "nsIDOMText.h"

#include "nsGridRowGroupLayout.h"

#define TICK_FACTOR 50

// the longest amount of time that can go by before the use
// notices it as a delay.
#define USER_TIME_THRESHOLD 150000

// how long it takes to layout a single row inital value.
// we will time this after we scroll a few rows.
#define TIME_PER_ROW_INITAL  50000

// if we decide we can't layout the rows in the amount of time. How long
// do we wait before checking again?
#define SMOOTH_INTERVAL 100


nsresult NS_NewAutoScrollTimer(nsXULTreeOuterGroupFrame* aTree, nsDragAutoScrollTimer **aResult) ;

//
// nsDragOverListener
//
// Just a little class that listens for dragOvers to trigger the auto-scrolling
// code.
//
class nsDragOverListener : public nsIDOMDragListener
{
public:

  nsDragOverListener ( nsXULTreeOuterGroupFrame* inTree )  
    : mTree ( inTree ) 
    {  NS_INIT_REFCNT(); }

  virtual ~nsDragOverListener() { } ;

  NS_DECL_ISUPPORTS

    // nsIDOMDragListener
  NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) { return NS_OK; }
  NS_IMETHOD DragEnter(nsIDOMEvent* aDragEvent) { return NS_OK; }
  NS_IMETHOD DragOver(nsIDOMEvent* aDragEvent);
  NS_IMETHOD DragExit(nsIDOMEvent* aDragEvent) { return NS_OK; }
  NS_IMETHOD DragDrop(nsIDOMEvent* aDragEvent) { return NS_OK; }
  NS_IMETHOD DragGesture(nsIDOMEvent* aDragEvent) { return NS_OK; }

protected:

  nsXULTreeOuterGroupFrame* mTree;

}; // nsDragEnterListener


NS_IMPL_ISUPPORTS2(nsDragOverListener, nsIDOMEventListener, nsIDOMDragListener)


//
// DragOver
//
// Kick off our timer/capturing for autoscrolling, the drag has entered us. We
// will continue capturing the mouse until the auto-scroll manager tells us to
// stop (either the drag is over, it left the window, or someone else wants a 
// crack at auto-scrolling).
//
nsresult
nsDragOverListener :: DragOver(nsIDOMEvent* aDragEvent)
{
  nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aDragEvent) );
  if ( mouseEvent ) {
    PRInt32 x = 0, y = 0;
    mouseEvent->GetClientX ( &x );
    mouseEvent->GetClientY ( &y );
    mTree->HandleAutoScrollTracking ( nsPoint(x,y) );
  }
  return NS_OK;

} // DragOver



#ifdef XP_MAC
#pragma mark -
#endif

/* A mediator used to smooth out scrolling. It works by seeing if 
 * we have time to scroll the amount of rows requested. This is determined
 * by measuring how long it takes to scroll a row. If we can scroll the 
 * rows in time we do so. If not we start a timer and skip the request. We
 * do this until the timer finally first because the user has stopped moving
 * the mouse. Then do all the queued requests in on shot.
 */
class nsScrollSmoother : public nsITimerCallback
{
public:

  NS_IMETHOD_(void) Notify(nsITimer *timer);

  void Start();
  void Stop();
  PRBool IsRunning();

  NS_DECL_ISUPPORTS
  virtual ~nsScrollSmoother();

  nsScrollSmoother(nsXULTreeOuterGroupFrame* aOuter);

  nsCOMPtr<nsITimer>         mRepeatTimer;
  PRBool mDelta;
  nsXULTreeOuterGroupFrame* mOuter;
}; 

nsScrollSmoother::nsScrollSmoother(nsXULTreeOuterGroupFrame* aOuter)
{
  NS_INIT_REFCNT();
  mDelta = 0;
  mOuter = aOuter;
}

nsScrollSmoother::~nsScrollSmoother()
{
  Stop();
}


PRBool nsScrollSmoother::IsRunning()
{
  if (mRepeatTimer)
    return PR_TRUE;
  else 
    return PR_FALSE;
}

void nsScrollSmoother::Start()
{
  Stop();
  mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1");
  mRepeatTimer->Init(this, SMOOTH_INTERVAL);
}

void nsScrollSmoother::Stop()
{
  if ( mRepeatTimer ) {
    mRepeatTimer->Cancel();
    mRepeatTimer = nsnull;
  }
}

NS_IMETHODIMP_(void) nsScrollSmoother::Notify(nsITimer *timer)
{
  //printf("Timer Callback!\n");

  Stop();

  NS_ASSERTION(mOuter, "mOuter is null, see bug #68365");
  if (!mOuter) return;

  // actually do some work.
  mOuter->InternalPositionChangedCallback();
}

NS_IMPL_ISUPPORTS1(nsScrollSmoother, nsITimerCallback)


//
// NS_NewXULTreeOuterGroupFrame
//
// Creates a new TreeOuterGroup frame
//
nsresult
NS_NewXULTreeOuterGroupFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRBool aIsRoot, 
                        nsIBoxLayout* aLayoutManager)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  if (nsnull == aNewFrame) {
    return NS_ERROR_NULL_POINTER;
  }
  nsXULTreeOuterGroupFrame* it = new (aPresShell) nsXULTreeOuterGroupFrame(aPresShell, aIsRoot, aLayoutManager);
  if (!it)
    return NS_ERROR_OUT_OF_MEMORY;

  *aNewFrame = it;
  return NS_OK;
  
} // NS_NewXULTreeOuterGroupFrame


// Constructor
nsXULTreeOuterGroupFrame::nsXULTreeOuterGroupFrame(nsIPresShell* aPresShell, PRBool aIsRoot, nsIBoxLayout* aLayoutManager)
  : nsXULTreeGroupFrame(aPresShell, aIsRoot, aLayoutManager),
    mBatchCount(0),
    mRowGroupInfo(nsnull),
    mRowHeight(0),
    mCurrentIndex(0),
    mOldIndex(0),
    mTreeIsSorted(PR_FALSE),
    mCanDropBetweenRows(PR_TRUE),
    mDragOverListener(nsnull),
    mRowHeightWasSet(PR_FALSE),
    mReflowCallbackPosted(PR_FALSE),
    mScrolling(PR_FALSE),
    mAdjustScroll(PR_FALSE),
    mYPosition(0),
    mScrollSmoother(nsnull),
    mTimePerRow(TIME_PER_ROW_INITAL),
    mTreeItemTag(nsXULAtoms::treeitem),
    mTreeRowTag(nsXULAtoms::treerow),
    mTreeChildrenTag(nsXULAtoms::treechildren),
    mStringWidth(-1)
{
}


NS_IMETHODIMP
nsXULTreeOuterGroupFrame::Destroy(nsIPresContext* aPresContext)
{
  
  // make sure we cancel any posted callbacks.
  if (mReflowCallbackPosted) {
     nsCOMPtr<nsIPresShell> shell;
     aPresContext->GetShell(getter_AddRefs(shell));
     shell->CancelReflowCallback(this);
  }
  

  return nsXULTreeGroupFrame::Destroy(aPresContext);
}

// Destructor
nsXULTreeOuterGroupFrame::~nsXULTreeOuterGroupFrame()
{
  NS_IF_RELEASE(mScrollSmoother);

  // TODO cancel posted events.

  nsCOMPtr<nsIContent> content;
  GetContent(getter_AddRefs(content));
  nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(content));

  // NOTE: the last Remove will delete the drag capturer
  if ( receiver && mDragOverListener ) 
    receiver->RemoveEventListener(NS_LITERAL_STRING("dragover"), mDragOverListener, PR_TRUE);
  
  delete mRowGroupInfo;

#if USE_TIMER_TO_DELAY_SCROLLING
  StopScrollTracking();
  mAutoScrollTimer = nsnull;
#endif

}

NS_IMETHODIMP_(nsrefcnt) 
nsXULTreeOuterGroupFrame::AddRef(void)
{
  return NS_OK;
}

NS_IMETHODIMP_(nsrefcnt)
nsXULTreeOuterGroupFrame::Release(void)
{
  return NS_OK;
}

//
// QueryInterface
//
NS_INTERFACE_MAP_BEGIN(nsXULTreeOuterGroupFrame)
  NS_INTERFACE_MAP_ENTRY(nsIScrollbarMediator)
  NS_INTERFACE_MAP_ENTRY(nsIReflowCallback)
NS_INTERFACE_MAP_END_INHERITING(nsXULTreeGroupFrame)


//
// Init
//
// Setup scrolling and event listeners for drag auto-scrolling
//
NS_IMETHODIMP
nsXULTreeOuterGroupFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent,
                               nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow)
{
  nsresult rv = nsXULTreeGroupFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);

  nsAutoString value;
  mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::treeitem, value);
  if (!value.IsEmpty())
    mTreeItemTag = getter_AddRefs(NS_NewAtom(value));
  mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::treerow, value);
  if (!value.IsEmpty())
    mTreeRowTag = getter_AddRefs(NS_NewAtom(value));
  mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::treechildren, value);
  if (!value.IsEmpty())
    mTreeChildrenTag = getter_AddRefs(NS_NewAtom(value));

 // mLayingOut = PR_FALSE;

  float p2t;
  aPresContext->GetScaledPixelsToTwips(&p2t);
  mOnePixel = NSIntPixelsToTwips(1, p2t);
  
  nsIFrame* box;
  aParent->GetParent(&box);
  if (!box)
    return rv;

  nsCOMPtr<nsIScrollableFrame> scrollFrame(do_QueryInterface(box));
  if (!scrollFrame)
    return rv;

  nsIScrollableView* scrollableView;
  scrollFrame->GetScrollableView(aPresContext, &scrollableView);
  scrollableView->SetScrollProperties(NS_SCROLL_PROPERTY_ALWAYS_BLIT);

  nsIBox* verticalScrollbar;
  scrollFrame->GetScrollbarBox(PR_TRUE, &verticalScrollbar);
  if (!verticalScrollbar) {
    NS_ERROR("Unable to install the scrollbar mediator on the tree widget. You must be using GFX scrollbars.");
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIScrollbarFrame> scrollbarFrame(do_QueryInterface(verticalScrollbar));
  scrollbarFrame->SetScrollbarMediator(this);

  nsBoxLayoutState boxLayoutState(aPresContext);

  const nsStyleFont* font = (const nsStyleFont*)aContext->GetStyleData(eStyleStruct_Font);
  nsCOMPtr<nsIDeviceContext> dc;
  aPresContext->GetDeviceContext(getter_AddRefs(dc));
  nsCOMPtr<nsIFontMetrics> fm;
  dc->GetMetricsFor(font->mFont, *getter_AddRefs(fm));
  fm->GetHeight(mRowHeight);

  // Our frame's lifetime is bounded by the lifetime of the content model, so we're guaranteed
  // that the content node won't go away on us. As a result, our listener can't go away before the
  // frame is deleted. Since the content node holds owning references to our drag capturer, which
  // we tear down in the dtor, there is no need to hold an owning ref to it ourselves.  
  nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(aContent));
  if ( receiver ) {
    mDragOverListener = new nsDragOverListener(this);
    receiver->AddEventListener(NS_LITERAL_STRING("dragover"), mDragOverListener, PR_FALSE);
  }
  
  // our parent is the <tree> tag. check if it has an attribute denying the ability to
  // drop between rows and cache it here for the benefit of the rows inside us.
  nsCOMPtr<nsIContent> parent;
  GetTreeContent(getter_AddRefs(parent));

  if ( parent ) {
    nsAutoString attr;
    parent->GetAttr ( kNameSpaceID_None, nsXULAtoms::ddNoDropBetweenRows, attr ); 
    if ( attr.Equals(NS_LITERAL_STRING("true")) )
      mCanDropBetweenRows = PR_FALSE;
  }
  
  return rv;

} // Init

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::NeedsRecalc()
{
  mStringWidth = -1;
  return nsXULTreeGroupFrame::NeedsRecalc();
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize)
{
  // NeedsRecalc();  // Don't think this is needed any more.
  return nsXULTreeGroupFrame::GetPrefSize(aBoxLayoutState, aSize);
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState)
{
  if (mScrolling)
    aBoxLayoutState.SetDisablePainting(PR_TRUE);

  nsresult rv = nsXULTreeGroupFrame::DoLayout(aBoxLayoutState);

  if (mScrolling)
    aBoxLayoutState.SetDisablePainting(PR_FALSE);

  // if we are scrolled and the row height changed
  // make sure we are scrolled to a correct index.
  if (mAdjustScroll) 
     PostReflowCallback();

  return rv;
}

PRInt32
nsXULTreeOuterGroupFrame::GetFixedRowSize()
{
  PRInt32 dummy;

  nsCOMPtr<nsIContent> parent;
  GetTreeContent(getter_AddRefs(parent));
  nsAutoString rows;
  parent->GetAttr(kNameSpaceID_None, nsXULAtoms::rows, rows);
  if (!rows.IsEmpty())
    return rows.ToInteger(&dummy);
 
  parent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::size, rows);

  if (!rows.IsEmpty())
    return rows.ToInteger(&dummy);

  return -1;
}

void
nsXULTreeOuterGroupFrame::SetRowHeight(nscoord aRowHeight)
{ 
  if (aRowHeight > mRowHeight) { 
    mRowHeight = aRowHeight;
    
    nsCOMPtr<nsIContent> parent;
    GetTreeContent(getter_AddRefs(parent));
    nsAutoString rows;
    parent->GetAttr(kNameSpaceID_None, nsXULAtoms::rows, rows);
    if (rows.IsEmpty())
      parent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::size, rows);
    
    if (!rows.IsEmpty()) {
      PRInt32 dummy;
      PRInt32 count = rows.ToInteger(&dummy);
      float t2p;
      mPresContext->GetTwipsToPixels(&t2p);
      PRInt32 rowHeight = NSTwipsToIntPixels(aRowHeight, t2p);
      nsAutoString value;
      value.AppendInt(rowHeight*count);
      mContent->SetAttr(kNameSpaceID_None, nsXULAtoms::minheight, value, PR_FALSE);
    }

    // signal we need to dirty everything 
    // and we want to be notified after reflow
    // so we can create or destory rows as needed
    mRowHeightWasSet = PR_TRUE;
    PostReflowCallback();
  } 
}

nscoord
nsXULTreeOuterGroupFrame::GetYPosition()
{
  return mYPosition;
}

void
nsXULTreeOuterGroupFrame::VerticalScroll(PRInt32 aPosition)
{
  nsIBox* box;
  GetParentBox(&box);
  if (!box)
    return;

  box->GetParentBox(&box);
  if (!box)
    return;

  nsCOMPtr<nsIScrollableFrame> scrollFrame(do_QueryInterface(box));
  if (!scrollFrame)
    return;

  nscoord x, y;
  scrollFrame->GetScrollPosition(mPresContext, x, y);
 
  scrollFrame->ScrollTo(mPresContext, x, aPosition, NS_SCROLL_PROPERTY_ALWAYS_BLIT);

  mYPosition = aPosition;
}

nscoord
nsXULTreeOuterGroupFrame::GetAvailableHeight()
{
  nsIBox* box;
  GetParentBox(&box);
  if (!box)
    return 0;

  nsRect contentRect;
  box->GetContentRect(contentRect);
  return contentRect.height;
}

void
nsXULTreeOuterGroupFrame::ComputeTotalRowCount(PRInt32& aCount, nsIContent* aParent)
{
  if (!mRowGroupInfo) {
    mRowGroupInfo = new nsXULTreeRowGroupInfo();
  }

  nsCOMPtr<nsIContent> parent = aParent;
  if (aParent == mContent) {
    nsCOMPtr<nsIContent> content;
    mContent->GetBindingParent(getter_AddRefs(content));
    if (content)
      GetTreeContent(getter_AddRefs(parent));
  }

  PRInt32 childCount;
  parent->ChildCount(childCount);

  for (PRInt32 i = 0; i < childCount; i++) {
    nsCOMPtr<nsIContent> childContent;
    parent->ChildAt(i, *getter_AddRefs(childContent));
    nsCOMPtr<nsIAtom> tag;
    childContent->GetTag(*getter_AddRefs(tag));
    if (tag == mTreeRowTag) {
      if ((aCount%TICK_FACTOR) == 0)
        mRowGroupInfo->Add(childContent);

      mRowGroupInfo->mLastChild = childContent;

      aCount++;
    }
    else if (tag == mTreeItemTag) {
      // Descend into this row group and try to find the next row.
      ComputeTotalRowCount(aCount, childContent);
    }
    else if (tag == mTreeChildrenTag) {
      // If it's open, descend into its treechildren.
      nsCOMPtr<nsIAtom> openAtom = dont_AddRef(NS_NewAtom("open"));
      nsAutoString isOpen;
      nsCOMPtr<nsIContent> parent;
      childContent->GetParent(*getter_AddRefs(parent));
      parent->GetAttr(kNameSpaceID_None, openAtom, isOpen);
      if (isOpen.Equals(NS_LITERAL_STRING("true")))
        ComputeTotalRowCount(aCount, childContent);
    }
  }
}

void
nsXULTreeOuterGroupFrame::PostReflowCallback()
{
  if (!mReflowCallbackPosted) {
    mReflowCallbackPosted = PR_TRUE;
    nsCOMPtr<nsIPresShell> shell;
    mPresContext->GetShell(getter_AddRefs(shell));
    shell->PostReflowCallback(this);
  }
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::VisibilityChanged(PRBool aVisible)
{
  if (!aVisible && mCurrentIndex > 0)
    EnsureRowIsVisible(0);

  return NS_OK;
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::ScrollbarButtonPressed(PRInt32 aOldIndex, PRInt32 aNewIndex)
{
  if (aOldIndex == aNewIndex)
    return NS_OK;
  if (aNewIndex < aOldIndex)
    mCurrentIndex--;
  else mCurrentIndex++;
  if (mCurrentIndex < 0) {
    mCurrentIndex = 0;
    return NS_OK;
  }
  InternalPositionChanged(aNewIndex < aOldIndex, 1);

  return NS_OK;
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::PositionChanged(PRInt32 aOldIndex, PRInt32& aNewIndex)
{ 
  if (mScrolling)
    return NS_OK;

  PRInt32 oldTwipIndex, newTwipIndex;
  oldTwipIndex = mCurrentIndex*mRowHeight;
  newTwipIndex = (aNewIndex*mOnePixel);
  PRInt32 twipDelta = newTwipIndex > oldTwipIndex ? newTwipIndex - oldTwipIndex : oldTwipIndex - newTwipIndex;

  PRInt32 rowDelta = twipDelta / mRowHeight;
  PRInt32 remainder = twipDelta % mRowHeight;
  if (remainder > (mRowHeight/2))
    rowDelta++;

  if (rowDelta == 0)
    return NS_OK;

  // update the position to be row based.

  PRInt32 newIndex = newTwipIndex > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta;
  //aNewIndex = newIndex*mRowHeight/mOnePixel;

  nsScrollSmoother* smoother = GetSmoother();

  //printf("%d rows, %d per row, Estimated time needed %d, Threshhold %d (%s)\n", rowDelta, mTimePerRow, mTimePerRow * rowDelta, USER_TIME_THRESHOLD, (mTimePerRow * rowDelta > USER_TIME_THRESHOLD) ? "Nope" : "Yep");

  // if we can't scroll the rows in time then start a timer. We will eat
  // events until the user stops moving and the timer stops.
  if (smoother->IsRunning() || rowDelta*mTimePerRow > USER_TIME_THRESHOLD) {

     smoother->Stop();

     nsCOMPtr<nsIPresShell> shell;
     mPresContext->GetShell(getter_AddRefs(shell));
     shell->FlushPendingNotifications(PR_FALSE);

     smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta;

     //printf("Eating scroll!\n");

     smoother->Start();

     return NS_OK;
  }

  smoother->Stop();

  mCurrentIndex = newIndex;
  smoother->mDelta = 0;
  
  if (mCurrentIndex < 0) {
    mCurrentIndex = 0;
    return NS_OK;
  }

  return InternalPositionChanged(newTwipIndex < oldTwipIndex, rowDelta);
}

nsScrollSmoother* 
nsXULTreeOuterGroupFrame::GetSmoother()
{
  if (!mScrollSmoother) {
    mScrollSmoother = new nsScrollSmoother(this);
    NS_ASSERTION(mScrollSmoother, "out of memory");
    NS_IF_ADDREF(mScrollSmoother);
  }

  return mScrollSmoother;
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::InternalPositionChangedCallback()
{
   nsScrollSmoother* smoother = GetSmoother();
   
   if (smoother->mDelta == 0)
     return NS_OK;

   mCurrentIndex += smoother->mDelta;

   if (mCurrentIndex < 0)
     mCurrentIndex = 0;

   return InternalPositionChanged(smoother->mDelta < 0, smoother->mDelta < 0 ? -smoother->mDelta : smoother->mDelta);
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::InternalPositionChanged(PRBool aUp, PRInt32 aDelta, PRBool aForceDestruct)
{  
  if (aDelta == 0)
    return NS_OK;

  // begin timing how long it takes to scroll a row
  PRTime start = PR_Now();

  //printf("Actually doing scroll mCurrentIndex=%d, delta=%d!\n", mCurrentIndex, aDelta);

  nsCOMPtr<nsIPresShell> shell;
  mPresContext->GetShell(getter_AddRefs(shell));
  shell->FlushPendingNotifications(PR_FALSE);

  PRInt32 visibleRows = 0;
  if (mRowHeight)
	  visibleRows = GetAvailableHeight()/mRowHeight;
  
  // Get our presentation context.
  if (aDelta < visibleRows && !aForceDestruct) {
    PRInt32 loseRows = aDelta;

    // scrolling down
    if (!aUp) {
      // Figure out how many rows we have to lose off the top.
      DestroyRows(loseRows);
    }
    // scrolling up
    else {
      // Get our first row content.
      nsCOMPtr<nsIContent> rowContent;
      GetFirstRowContent(getter_AddRefs(rowContent));

      // Figure out how many rows we have to lose off the bottom.
      ReverseDestroyRows(loseRows);
    
      // Now that we've lost some rows, we need to create a
      // content chain that provides a hint for moving forward.
      nsCOMPtr<nsIContent> topRowContent;
      PRInt32 findContent = aDelta;
      FindPreviousRowContent(findContent, rowContent, nsnull, getter_AddRefs(topRowContent));
      ConstructContentChain(topRowContent);
	    //Now construct the chain for the old top row so its content chain gets
	    //set up correctly.
	    ConstructOldContentChain(rowContent);
    }
  }
  else {
    // Just blow away all our frames, but keep a content chain
    // as a hint to figure out how to build the frames.
    // Remove the scrollbar first.
    // get the starting row index and row count
    nsIBox* currBox;
    GetChildBox(&currBox);
    while (currBox) {
      nsIBox* nextBox;
      currBox->GetNextBox(&nextBox);
      nsIFrame* frame;
      currBox->QueryInterface(NS_GET_IID(nsIFrame), (void**)&frame); 
      mFrameConstructor->RemoveMappingsForFrameSubtree(mPresContext, frame, nsnull);
      currBox = nextBox;
    }

    nsBoxLayoutState state(mPresContext);
    ClearChildren(state);
    mFrames.DestroyFrames(mPresContext);

    nsCOMPtr<nsIContent> topRowContent;
    FindRowContentAtIndex(mCurrentIndex, mContent, getter_AddRefs(topRowContent));

    if (topRowContent)
      ConstructContentChain(topRowContent);
  }

  mTopFrame = mBottomFrame = nsnull; // Make sure everything is cleared out.
  
  mYPosition = mCurrentIndex*mRowHeight;
  nsBoxLayoutState state(mPresContext);
  mScrolling = PR_TRUE;
  MarkDirtyChildren(state);
  shell->FlushPendingNotifications(PR_FALSE);
  mScrolling = PR_FALSE;
  
  VerticalScroll(mYPosition);

  if (aForceDestruct)
    Redraw(state, nsnull, PR_FALSE);
  
  PRTime end = PR_Now();

  PRTime difTime;
  LL_SUB(difTime, end, start);

  PRInt32 newTime;
  LL_L2I(newTime, difTime);
  newTime /= aDelta;

  // average old and new
  mTimePerRow = (newTime + mTimePerRow)/2;
  
  //printf("time per row=%d\n", mTimePerRow);

  return NS_OK;
}


void 
nsXULTreeOuterGroupFrame::ConstructContentChain(nsIContent* aRowContent)
{
  // Create the content chain array.
  NS_IF_RELEASE(mContentChain);
  NS_NewISupportsArray(&mContentChain);

  nsCOMPtr<nsIContent> treeContent;
  GetTreeContent(getter_AddRefs(treeContent));

  // Move up the chain until we hit our content node.
  nsCOMPtr<nsIContent> currContent = dont_QueryInterface(aRowContent);
  while (currContent && (currContent.get() != mContent) &&
         currContent != treeContent) {
    mContentChain->InsertElementAt(currContent, 0);
    nsCOMPtr<nsIContent> otherContent = currContent;
    otherContent->GetParent(*getter_AddRefs(currContent));
  }

  NS_ASSERTION(currContent.get() == mContent || currContent == treeContent, 
               "Disaster! Content not contained in our tree!\n");
}

void 
nsXULTreeOuterGroupFrame::ConstructOldContentChain(nsIContent* aOldRowContent)
{
	nsCOMPtr<nsIContent> childOfCommonAncestor;

	//Find the first child of the common ancestor between the new top row's content chain
	//and the old top row.  Everything between this child and the old top row potentially need
	//to have their content chains reset.
	FindChildOfCommonContentChainAncestor(aOldRowContent, getter_AddRefs(childOfCommonAncestor));

	if (childOfCommonAncestor) {
      //Set up the old top rows content chian.
	  CreateOldContentChain(aOldRowContent, childOfCommonAncestor);
	}
}

void
nsXULTreeOuterGroupFrame::FindChildOfCommonContentChainAncestor(nsIContent *startContent, nsIContent **child)
{
	PRUint32 count;
	if (mContentChain)
	{
	  nsresult rv = mContentChain->Count(&count);

	  if (NS_SUCCEEDED(rv) && (count >0)) {
	    for (PRInt32 curItem = count - 1; curItem >= 0; curItem--) {
		    nsCOMPtr<nsISupports> supports;
        mContentChain->GetElementAt(curItem, getter_AddRefs(supports));
        nsCOMPtr<nsIContent> curContent = do_QueryInterface(supports);

		    //See if curContent is an ancestor of startContent.
		    if (IsAncestor(curContent, startContent, child))
		      return;
		  }
	  }
	}

	//mContent isn't actually put in the content chain, so we need to
	//check it separately.
	if (IsAncestor(mContent, startContent, child))
		return;

	*child = nsnull;
}


// if oldRowContent is an ancestor of rowContent, return true,
// and return the previous ancestor if requested
PRBool
nsXULTreeOuterGroupFrame::IsAncestor(nsIContent *aRowContent, nsIContent *aOldRowContent, nsIContent** firstDescendant)
{
  nsCOMPtr<nsIContent> prevContent;	
  nsCOMPtr<nsIContent> currContent = dont_QueryInterface(aOldRowContent);
  
  while (currContent) {
	  if (aRowContent == currContent.get()) {
		  if (firstDescendant) {
		    *firstDescendant = prevContent;
		    NS_IF_ADDREF(*firstDescendant);
		  }
		  return PR_TRUE;
	  }
    
	  prevContent = currContent;
	  prevContent->GetParent(*getter_AddRefs(currContent));
  }

  return PR_FALSE;
}

void
nsXULTreeOuterGroupFrame::CreateOldContentChain(nsIContent* aOldRowContent, nsIContent* topOfChain)
{
  nsCOMPtr<nsIContent> currContent = dont_QueryInterface(aOldRowContent);
  nsCOMPtr<nsIContent> prevContent;

  nsCOMPtr<nsIPresShell> shell;
  mPresContext->GetShell(getter_AddRefs(shell));

  //For each item between the (oldtoprow) and
  // (the new first child of common ancestry between new top row and old top row)
  // we need to see if the content chain has to be reset.
  while (currContent.get() != topOfChain) {
    nsIFrame* primaryFrame = nsnull;
    shell->GetPrimaryFrameFor(currContent, &primaryFrame);
      
    if (primaryFrame) {
      nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(primaryFrame));
      PRBool isRowGroup = PR_FALSE;
      if (slice)
        slice->IsGroupFrame(&isRowGroup);
    
	    if (isRowGroup) {
		    //Get the current content's parent's first child
		    nsCOMPtr<nsIContent> parent;
		    currContent->GetParent(*getter_AddRefs(parent));
		    
		    nsCOMPtr<nsIContent> firstChild;
		    parent->ChildAt(0, *getter_AddRefs(firstChild));

        nsIFrame* parentFrame;
        primaryFrame->GetParent(&parentFrame);
        PRBool isParentRowGroup = PR_FALSE;
        nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(parentFrame));
        if (slice)
          slice->IsGroupFrame(&isParentRowGroup);
    
	      if (isParentRowGroup) {
           //Get the current content's parent's first frame.
           nsXULTreeGroupFrame *parentRowGroupFrame =
               (nsXULTreeGroupFrame*)parentFrame;
		       nsIFrame *currentTopFrame = parentRowGroupFrame->GetFirstFrame();

			     nsCOMPtr<nsIContent> topContent;
			     currentTopFrame->GetContent(getter_AddRefs(topContent));

			     // If the current content's parent's first child is different
           // than the current frame's parent's first child then we know
           // they are out of synch and we need to set the content
           // chain correctly.
			     if(topContent.get() != firstChild.get()) {
				     nsCOMPtr<nsISupportsArray> contentChain;
				     NS_NewISupportsArray(getter_AddRefs(contentChain));
             contentChain->InsertElementAt(firstChild, 0);
             parentRowGroupFrame->SetContentChain(contentChain);
           }
        }
      }
    }

	  prevContent = currContent;
	  prevContent->GetParent(*getter_AddRefs(currContent));
  }
}

void
nsXULTreeOuterGroupFrame::FindRowContentAtIndex(PRInt32& aIndex,
                                                nsIContent* aParent,
                                                nsIContent** aResult)
{
  // Init to nsnull.
  *aResult = nsnull;

  // Walk over the tick array.
  if (mRowGroupInfo == nsnull)
    return;

  PRUint32 index = 0;
  PRUint32 arrayCount;
  mRowGroupInfo->mTickArray->Count(&arrayCount);
  nsCOMPtr<nsIContent> startContent;
  PRUint32 location = aIndex/TICK_FACTOR + 1;
  PRUint32 point = location*TICK_FACTOR;
  if (location >= arrayCount) {
    startContent = mRowGroupInfo->mLastChild;
    point = mRowGroupInfo->mRowCount-1;
  }
  else {
    nsCOMPtr<nsISupports> supp = getter_AddRefs(mRowGroupInfo->mTickArray->ElementAt(location));
    startContent = do_QueryInterface(supp);
  }

  if (!startContent) {
    NS_ERROR("The tree's tick array is confused!");
    return;
  }

  PRInt32 delta = (PRInt32)(point-aIndex);
  if (delta == 0) {
    *aResult = startContent;
    NS_IF_ADDREF(*aResult);
  }
  else FindPreviousRowContent(delta, startContent, nsnull, aResult);
}

void 
nsXULTreeOuterGroupFrame::FindPreviousRowContent(PRInt32& aDelta, nsIContent* aUpwardHint, 
                                                 nsIContent* aDownwardHint,
                                                 nsIContent** aResult)
{
  // Init to nsnull.
  *aResult = nsnull;

  // It disappoints me that this function is completely tied to the content nodes,
  // but I can't see any other way to handle this.  I don't have the frames, so I have nothing
  // else to fall back on but the content nodes.
  PRInt32 index = 0;
  nsCOMPtr<nsIContent> parentContent;
  if (aUpwardHint) {
    aUpwardHint->GetParent(*getter_AddRefs(parentContent));
    NS_ASSERTION(parentContent, "Parent content null in the upward hint of FPRC\n");
    if (!parentContent)
      return;
    parentContent->IndexOf(aUpwardHint, index);
  }
  else if (aDownwardHint) {
    parentContent = dont_QueryInterface(aDownwardHint);
    parentContent->ChildCount(index);
  }

  for (PRInt32 i = index-1; i >= 0; i--) {
    nsCOMPtr<nsIContent> childContent;
    parentContent->ChildAt(i, *getter_AddRefs(childContent));
    nsCOMPtr<nsIAtom> tag;
    childContent->GetTag(*getter_AddRefs(tag));
    if (tag == mTreeRowTag) {
      aDelta--;
      if (aDelta == 0) {
        *aResult = childContent;
        NS_IF_ADDREF(*aResult);
        return;
      }
    }
    else if (tag == mTreeItemTag) {
      // If it's open, descend into its treechildren node first.
      nsCOMPtr<nsIAtom> openAtom = dont_AddRef(NS_NewAtom("open"));
      nsAutoString isOpen;
      childContent->GetAttr(kNameSpaceID_None, openAtom, isOpen);
      if (isOpen.Equals(NS_LITERAL_STRING("true"))) {
        // Find the <treechildren> node.
        PRInt32 childContentCount;
        nsCOMPtr<nsIContent> grandChild;
        childContent->ChildCount(childContentCount);

        PRInt32 j;
        for (j = childContentCount-1; j >= 0; j--) {
          
          childContent->ChildAt(j, *getter_AddRefs(grandChild));
          nsCOMPtr<nsIAtom> grandChildTag;
          grandChild->GetTag(*getter_AddRefs(grandChildTag));
          if (grandChildTag == mTreeChildrenTag)
            break;
        }
        if (j >= 0 && grandChild)
          FindPreviousRowContent(aDelta, nsnull, grandChild, aResult);
      
        if (aDelta == 0)
          return;
      }

      // Descend into this row group and try to find a previous row.
      FindPreviousRowContent(aDelta, nsnull, childContent, aResult);
      if (aDelta == 0)
        return;
    }
  }

  NS_ASSERTION(parentContent, "Parent content null at the end of FPRC\n");
  if (!parentContent)
    return;

  nsCOMPtr<nsIAtom> tag;
  parentContent->GetTag(*getter_AddRefs(tag));
  if (tag && tag.get() == nsXULAtoms::tree) {
    // Hopeless. It ain't in there.
    return;
  }
  else if (!aDownwardHint) // We didn't find it here. We need to go up to our parent, using ourselves as a hint.
    FindPreviousRowContent(aDelta, parentContent, nsnull, aResult);

  // Bail. There's nothing else we can do.
}

void 
nsXULTreeOuterGroupFrame::FindNextRowContent(PRInt32& aDelta, nsIContent* aUpwardHint, 
                                             nsIContent* aDownwardHint,
                                             nsIContent** aResult)
{
  // Init to nsnull.
  *aResult = nsnull;

  // It disappoints me that this function is completely tied to the content nodes,
  // but I can't see any other way to handle this.  I don't have the frames, so I have nothing
  // else to fall back on but the content nodes.
  PRInt32 index = -1;
  nsCOMPtr<nsIContent> parentContent;
  if (aUpwardHint) {
    aUpwardHint->GetParent(*getter_AddRefs(parentContent));
    if (!parentContent) {
      NS_ERROR("Parent content should not be NULL!");
      return;
    }
    parentContent->IndexOf(aUpwardHint, index);
  }
  else if (aDownwardHint) {
    parentContent = dont_QueryInterface(aDownwardHint);
  }

  PRInt32 childCount;
  parentContent->ChildCount(childCount);
  for (PRInt32 i = index+1; i < childCount; i++) {
    nsCOMPtr<nsIContent> childContent;
    parentContent->ChildAt(i, *getter_AddRefs(childContent));
    nsCOMPtr<nsIAtom> tag;
    childContent->GetTag(*getter_AddRefs(tag));
    if (tag == mTreeRowTag) {
      aDelta--;
      if (aDelta == 0) {
        *aResult = childContent;
        NS_IF_ADDREF(*aResult);
        return;
      }
    }
    else if (tag == mTreeItemTag) {
      // Descend into this row group and try to find a next row.
      FindNextRowContent(aDelta, nsnull, childContent, aResult);
      if (aDelta == 0)
        return;
    }
    else if (tag == mTreeChildrenTag) {
      // If it's open, descend into its treechildren node first.
      nsCOMPtr<nsIAtom> openAtom = dont_AddRef(NS_NewAtom("open"));
      nsAutoString isOpen;
      parentContent->GetAttr(kNameSpaceID_None, openAtom, isOpen);
      if (isOpen.Equals(NS_LITERAL_STRING("true"))) {
        FindNextRowContent(aDelta, nsnull, childContent, aResult);
        if (aDelta == 0)
          return;
      }
    }
  }

  nsCOMPtr<nsIAtom> tag;
  parentContent->GetTag(*getter_AddRefs(tag));
  if (tag && tag.get() == nsXULAtoms::tree) {
    // Hopeless. It ain't in there.
    return;
  }
  else if (!aDownwardHint) // We didn't find it here. We need to go up to our parent, using ourselves as a hint.
    FindNextRowContent(aDelta, parentContent, nsnull, aResult);

  // Bail. There's nothing else we can do.
}

void
nsXULTreeOuterGroupFrame::EnsureRowIsVisible(PRInt32 aRowIndex)
{
  NS_ASSERTION(aRowIndex >= 0, "Ensure row is visible called with a negative number!");
  if (aRowIndex < 0)
    return;

  PRInt32 rows = 0;
  if (mRowHeight)
    rows = GetAvailableHeight()/mRowHeight;
  PRInt32 bottomIndex = mCurrentIndex + rows;
  
  // if row is visible, ignore
  if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex)
    return;

  // Check to be sure we're not scrolling off the bottom of the tree
  PRInt32 delta;

  PRBool up = aRowIndex < mCurrentIndex;
  if (up) {
    delta = mCurrentIndex - aRowIndex;
    mCurrentIndex = aRowIndex;
  }
  else {
    // Bring it just into view.
    delta = 1 + (aRowIndex-bottomIndex);
    mCurrentIndex += delta; 
  }

  InternalPositionChanged(up, delta);
}

void
nsXULTreeOuterGroupFrame::ScrollToIndex(PRInt32 aRowIndex, PRBool aForceDestruct)
{
  if (( aRowIndex < 0 ) || (mRowHeight == 0))
    return;
    
  PRInt32 newIndex = aRowIndex;
  PRInt32 delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
  PRBool up = newIndex < mCurrentIndex;

  // Check to be sure we're not scrolling off the bottom of the tree
  PRInt32 lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
  if (lastPageTopRow < 0)
    lastPageTopRow = 0;

  if (aRowIndex > lastPageTopRow)
    return;

  mCurrentIndex = newIndex;
  InternalPositionChanged(up, delta, aForceDestruct);

  // This change has to happen immediately.
  // Flush any pending reflow commands.
  nsCOMPtr<nsIDocument> doc;
  mContent->GetDocument(*getter_AddRefs(doc));
  doc->FlushPendingNotifications();
}

// walks the DOM to get the zero-based row index of the current content
// note that aContent can be any element, this will get the index of the
// element's parent
NS_IMETHODIMP
nsXULTreeOuterGroupFrame::IndexOfItem(nsIContent* aRoot, nsIContent* aContent,
                                      PRBool aDescendIntoRows, // Invariant
                                      PRBool aParentIsOpen,
                                      PRInt32 *aResult)
{
  PRInt32 childCount=0;
  aRoot->ChildCount(childCount);

  nsresult rv;
  
  PRInt32 childIndex;
  for (childIndex=0; childIndex<childCount; childIndex++) {
    nsCOMPtr<nsIContent> child;
    aRoot->ChildAt(childIndex, *getter_AddRefs(child));
    
    nsCOMPtr<nsIAtom> childTag;
    child->GetTag(*getter_AddRefs(childTag));

    // is this it?
    if (child.get() == aContent)
      return NS_OK;

    // we hit a treerow, count it
    if (childTag == mTreeItemTag)
      (*aResult)++;
  
    PRBool descend = PR_TRUE;
    PRBool parentIsOpen = aParentIsOpen;

    // don't descend into closed children
    if (childTag == mTreeChildrenTag && !parentIsOpen)
      descend = PR_FALSE;

    // speed optimization - descend into rows only when told
    else if (childTag == mTreeRowTag && !aDescendIntoRows)
      descend = PR_FALSE;

    // descend as normally, but remember that the parent is closed!
    else if (childTag == mTreeItemTag) {
      nsAutoString isOpen;
      rv = child->GetAttr(kNameSpaceID_None, nsXULAtoms::open, isOpen);

      if (!isOpen.Equals(NS_LITERAL_STRING("true")))
        parentIsOpen=PR_FALSE;
    }

    // now that we've analyzed the tags, recurse
    if (descend) {
      rv = IndexOfItem(child, aContent,
                       aDescendIntoRows, parentIsOpen, aResult);
      if (NS_SUCCEEDED(rv))
        return NS_OK;
    }
  }

  // not found
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::EndBatch()
{
  NS_ASSERTION(mBatchCount, "EndBatch called on a tree that isn't batching!\n");
  if (mBatchCount == 0)
    return NS_OK;

  mBatchCount--;
  if (mBatchCount == 0) {
    if (mCurrentIndex == mOldIndex) {
      nsBoxLayoutState state(mPresContext);
      MarkDirtyChildren(state);
    }
    else ScrollToIndex(mCurrentIndex, PR_TRUE);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsXULTreeOuterGroupFrame::ReflowFinished(nsIPresShell* aPresShell, PRBool* aFlushFlag)
{
  // Now dirty the world.
  nsCOMPtr<nsIContent> tree;
  GetTreeContent(getter_AddRefs(tree));
  
  nsIFrame* treeFrame;
  aPresShell->GetPrimaryFrameFor(tree, &treeFrame);

  nsCOMPtr<nsIBox> treeBox(do_QueryInterface(treeFrame));

  nsBoxLayoutState state(mPresContext);

  // now build or destroy any needed rows.
  nsCOMPtr<nsIBoxLayout> layout;
  GetLayoutManager(getter_AddRefs(layout));
  nsTreeLayout* treeLayout = (nsTreeLayout*)layout.get();
  treeLayout->LazyRowCreator(state, this);

  if (mAdjustScroll) {
     VerticalScroll(mYPosition);
     mAdjustScroll = PR_FALSE;
  }

  // if the row height changed
  // then mark everything as a style change. That
  // will dirty the tree all the way to its leaves.
  if (mRowHeightWasSet) {
     if (!treeBox)
        return NS_ERROR_NULL_POINTER;
	 treeBox->MarkStyleChange(state);
     PRInt32 pos = mCurrentIndex*mRowHeight;
     if (mYPosition != pos) 
       mAdjustScroll = PR_TRUE;

    mRowHeightWasSet = PR_FALSE;
  }

  mReflowCallbackPosted = PR_FALSE;

  *aFlushFlag = PR_TRUE;
  
  return NS_OK;
}

void
nsXULTreeOuterGroupFrame::RegenerateRowGroupInfo(PRBool aOnScreenCount)
{ 
  NeedsRecalc(); 
  
  PRInt32 oldRowCount = GetRowCount();
  if (mRowGroupInfo) 
    mRowGroupInfo->Clear(); 
  PRInt32 newRowCount = GetRowCount();

  if (mRowHeight <= 0)
      return;

  // For removal only, we need to know how many rows are onscreen.  These subtract
  // from the amount that we need to adjust.  For example, if the tree widget is
  // scrolled to index 40, and if a folder that is offscreen at index 0
  // is deleted, and it contains 9 kids, then a total of 10 rows are vanishing.  
  // newRowCount - oldRowCount will be -10 following the removal.  However, if 4 of those
  // 10 rows were onscreen, then the tree widget's index only needs to be adjusted by
  // -6 (-10 + 4).  
  PRInt32 delta = newRowCount-oldRowCount+aOnScreenCount;
  PRInt32 newIndex = mCurrentIndex + delta;
  PRBool adjust = PR_FALSE;

  // Check to be sure we're not scrolling off the bottom of the tree
  PRInt32 lastPageTopRow = newRowCount - (GetAvailableHeight() / mRowHeight);
  if (lastPageTopRow < 0) {
    if (aOnScreenCount > 0)
      adjust = PR_TRUE;
    lastPageTopRow = 0;
  }
  
  if (newIndex > lastPageTopRow) { 
    newIndex = lastPageTopRow;
    if (aOnScreenCount > 0)
      adjust = PR_TRUE;
  }

  if (newIndex < 0)
    newIndex = 0;

  if (!adjust) {
    if (mCurrentIndex == 0 || delta == 0)
      return; // Just a simple update or we aren't scrolled, so bail.

    nsCOMPtr<nsIContent> row;
    GetFirstRowContent(getter_AddRefs(row));
    NS_ASSERTION(row, "No row in regen check!");
    if (!row)
      return;

    // An element was passed in that was either removed or added.
    // We need to adjust our scroll position if this element's index
    // is < our current scrolled index.
    PRInt32 index = 0;
    nsCOMPtr<nsIContent> item;
    row->GetParent(*getter_AddRefs(item));
    IndexOfItem(mContent, item, PR_FALSE, PR_TRUE, &index);
    if (index == -1 || index == mCurrentIndex)
      return;
  }
  
  // We mark the outer row group dirty and force a comprehensive
  // rebuild of all tree widget frames.  This ensures that we
  // stay precisely in sync whenever we lose content from above.
  if (IsBatching())
    mCurrentIndex = newIndex;
  else ScrollToIndex(newIndex, !adjust);

  if (adjust) {
    // Force a full redraw.
    nsBoxLayoutState state(mPresContext);
    Redraw(state, nsnull, PR_FALSE);
  }
}

void
nsXULTreeOuterGroupFrame::GetTreeContent(nsIContent** aResult)
{
  nsCOMPtr<nsIContent> content(mContent);
  nsCOMPtr<nsIContent> bindingParent;
  mContent->GetBindingParent(getter_AddRefs(bindingParent));
  if (bindingParent) {
    nsCOMPtr<nsIDocument> doc;
    bindingParent->GetDocument(*getter_AddRefs(doc));
    nsCOMPtr<nsIBindingManager> bindingManager;
    doc->GetBindingManager(getter_AddRefs(bindingManager));
    nsCOMPtr<nsIAtom> tag;
    PRInt32 namespaceID;
    bindingManager->ResolveTag(bindingParent, &namespaceID, getter_AddRefs(tag));
    if (tag.get() == nsXULAtoms::tree) {
      *aResult = bindingParent;
      NS_ADDREF(*aResult);
    }
  }
  else 
    mContent->GetParent(*aResult); // method does the addref
}


//
// Paint
//
// Overridden to handle the case where we should be drawing the tree as sorted.
//
NS_IMETHODIMP
nsXULTreeOuterGroupFrame :: Paint ( nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext,
                                      const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer)
{
  nsresult res = NS_OK;
  
  res = nsBoxFrame::Paint ( aPresContext, aRenderingContext, aDirtyRect, aWhichLayer );
  
  if ( (aWhichLayer == eFramePaintLayer_Content) &&
        (mYDropLoc != nsTreeItemDragCapturer::kNoDropLoc || mDropOnContainer || mTreeIsSorted) )
    PaintDropFeedback ( aPresContext, aRenderingContext, PR_TRUE );

  return res;

} // Paint


//
// AttributeChanged
//
// Track several attributes set by the d&d drop feedback tracking mechanism, notably
// telling us to paint as sorted
//
NS_IMETHODIMP
nsXULTreeOuterGroupFrame :: AttributeChanged ( nsIPresContext* aPresContext, nsIContent* aChild,
                                                 PRInt32 aNameSpaceID, nsIAtom* aAttribute, 
                                                 PRInt32 aModType, PRInt32 aHint)
{
  nsresult rv = NS_OK;
   
  if ( aAttribute == nsXULAtoms::ddTriggerRepaintSorted ) {
    // Set a flag so that children won't draw the drop feedback but the parent
    // will.
    mTreeIsSorted = PR_TRUE;
    ForceDrawFrame ( aPresContext, this );
    mTreeIsSorted = PR_FALSE;
  }
  else
    rv = nsXULTreeGroupFrame::AttributeChanged ( aPresContext, aChild, aNameSpaceID, aAttribute, aModType, aHint );

  return rv;
 
} // AttributeChanged



#ifdef XP_MAC
#pragma mark -
#endif



//
// HandleAutoScrollTracking
//
//
nsresult
nsXULTreeOuterGroupFrame :: HandleAutoScrollTracking ( const nsPoint & aPoint )
{
#if USE_TIMER_TO_DELAY_SCROLLING
// if we're in the scroll region and there is no timer, start one and return
// if the timer has started but not yet fired, do nothing
// if the timer has fired, scroll the direction it tells us.
  PRBool scrollUp = PR_FALSE;
  if ( IsInDragScrollRegion(aPoint, &scrollUp) ) {
    if ( mAutoScrollTimerStarted ) {
printf("waiting...\n");
      if ( mAutoScrollTimerHasFired ) {
        // we're in the right area and the timer has fired, so scroll
        ScrollToIndex ( scrollUp ? mCurrentIndex - 1 : mCurrentIndex + 1);
      }
    }
    else {
printf("starting timer\n");
      // timer hasn't started yet, kick it off. Remember, this timer is a one-shot
      mAutoScrollTimer = do_CreateInstance("@mozilla.org/timer;1");
      if ( mAutoScrollTimer ) {
        mAutoScrollTimer->Init(this, 100);
        mAutoScrollTimerStarted = PR_TRUE;
      }
      else
          return NS_ERROR_FAILURE;
    }
  } // we're in an area we care about
  else
    StopScrollTracking();
    
#else

  PRBool scrollUp = PR_FALSE;
  if ( IsInDragScrollRegion(aPoint, &scrollUp) )
    ScrollToIndex ( scrollUp ? mCurrentIndex - 1 : mCurrentIndex + 1);
    
#endif

  return NS_OK;

} // HandleAutoScrollTracking


//
// IsInDragScrollRegion
//
// Determine if the current point is inside one of the scroll regions near the top
// or bottom of the tree and will return PR_TRUE if it is, PR_FALSE if it isn't. 
// If it is, this will set |outScrollUp| to PR_TRUE if the tree should scroll up,
// PR_FALSE if it should scroll down.
//
PRBool
nsXULTreeOuterGroupFrame :: IsInDragScrollRegion ( const nsPoint& inPoint, PRBool* outScrollUp )
{
  PRBool isInRegion = PR_FALSE;

  float pixelsToTwips = 0.0;
  mPresContext->GetPixelsToTwips ( &pixelsToTwips );
  nsPoint mouseInTwips ( NSToIntRound(inPoint.x * pixelsToTwips), NSToIntRound(inPoint.y * pixelsToTwips) );
  
  // compute the offset to top level in twips and subtract the offset from
  // the mouse coord to put it into our coordinates.
  nscoord frameOffsetX = 0, frameOffsetY = 0;
  nsIFrame* curr = this;
  curr->GetParent(&curr);
  while ( curr ) {
    nsPoint origin;
    curr->GetOrigin(origin);      // in twips    
    frameOffsetX += origin.x;     // build the offset incrementally
    frameOffsetY += origin.y;    
    curr->GetParent(&curr);       // moving up the chain
  } // until we reach the top  
  mouseInTwips.MoveBy ( -frameOffsetX, -frameOffsetY );

  const int kMarginHeight = NSToIntRound ( 12 * pixelsToTwips );

  // scroll if we're inside the bounds of the tree and w/in |kMarginHeight|
  // from the top or bottom of the tree.
  if ( mRect.Contains(mouseInTwips) ) {
    if ( mouseInTwips.y <= kMarginHeight ) {
      isInRegion = PR_TRUE;
      if ( outScrollUp )
        *outScrollUp = PR_TRUE;      // scroll up
    }
    else if ( mouseInTwips.y > GetAvailableHeight() - kMarginHeight ) {
      isInRegion = PR_TRUE;
      if ( outScrollUp )
        *outScrollUp = PR_FALSE;     // scroll down
    }
  }
  
  return isInRegion;
  
} // IsInDragScrollRegion


#if USE_TIMER_TO_DELAY_SCROLLING

//
// Notify
//
// Called when the timer fires. Tells the tree that we're ready to scroll if the 
// mouse is in the right position.
//
void
nsXULTreeOuterGroupFrame :: Notify ( nsITimer* timer )
{
printf("000 FIRE\n");
  mAutoScrollTimerHasFired = PR_TRUE;
}

nsresult
nsXULTreeOuterGroupFrame :: StopScrollTracking()
{
printf("--stop\n");
  if ( mAutoScrollTimer && mAutoScrollTimerStarted )
    mAutoScrollTimer->Cancel();
  mAutoScrollTimerHasFired = PR_FALSE;
  mAutoScrollTimerStarted = PR_FALSE;
  
  return NS_OK;
}

#endif

NS_IMETHODIMP 
nsXULTreeOuterGroupFrame::SizeTo(nsIPresContext* aPresContext, nscoord aWidth, nscoord aHeight)
{
  return nsXULTreeGroupFrame::SizeTo(aPresContext, aWidth, aHeight);
}

class nsTreeScrollPortFrame : public nsScrollPortFrame
{
public:
  nsTreeScrollPortFrame(nsIPresShell* aShell);
  friend nsresult NS_NewScrollBoxFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame);
  NS_IMETHOD GetPrefSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize);
  NS_IMETHOD GetMinSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize);
};

// Scrollport Subclass
nsresult
NS_NewTreeScrollPortFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  if (nsnull == aNewFrame) {
    return NS_ERROR_NULL_POINTER;
  }
  nsTreeScrollPortFrame* it = new (aPresShell) nsTreeScrollPortFrame (aPresShell);
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  *aNewFrame = it;
  return NS_OK;
}

nsTreeScrollPortFrame::nsTreeScrollPortFrame(nsIPresShell* aShell):nsScrollPortFrame(aShell)
{
}

NS_IMETHODIMP
nsTreeScrollPortFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize)
{  
  nsIBox* child = nsnull;
  GetChildBox(&child);
 
  nsresult rv = child->GetPrefSize(aBoxLayoutState, aSize);
  nsXULTreeOuterGroupFrame* outer = NS_STATIC_CAST(nsXULTreeOuterGroupFrame*,child);

  nsAutoString sizeMode;
  nsCOMPtr<nsIContent> content;
  outer->GetContent(getter_AddRefs(content));
  content->GetAttr(kNameSpaceID_None, nsXULAtoms::sizemode, sizeMode);
  if (!sizeMode.IsEmpty()) {  
    nsCOMPtr<nsIScrollableFrame> scrollFrame(do_QueryInterface(mParent));
    if (scrollFrame) {
      nsIScrollableFrame::nsScrollPref scrollPref;
      scrollFrame->GetScrollPreference(aBoxLayoutState.GetPresContext(), &scrollPref);

      if (scrollPref == nsIScrollableFrame::Auto) {
        nscoord vbarwidth, hbarheight;
        scrollFrame->GetScrollbarSizes(aBoxLayoutState.GetPresContext(),
                                       &vbarwidth, &hbarheight);
        aSize.width += vbarwidth;
      }
    }
  }
  else aSize.width = 0;

  aSize.height = 0;
  
  AddMargin(child, aSize);
  AddBorderAndPadding(aSize);
  AddInset(aSize);
  nsIBox::AddCSSMinSize(aBoxLayoutState, this, aSize);
  return rv;

}

NS_IMETHODIMP
nsTreeScrollPortFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize)
{  
  nsIBox* child = nsnull;
  GetChildBox(&child);
 
  nsresult rv = child->GetPrefSize(aBoxLayoutState, aSize);
  nsXULTreeOuterGroupFrame* outer = NS_STATIC_CAST(nsXULTreeOuterGroupFrame*,child);

  PRInt32 size = outer->GetFixedRowSize();

  if (size > -1)
    aSize.height = size*outer->GetRowHeightTwips();
   
  nsCOMPtr<nsIScrollableFrame> scrollFrame(do_QueryInterface(mParent));
  if (scrollFrame) {
    nsIScrollableFrame::nsScrollPref scrollPref;
    scrollFrame->GetScrollPreference(aBoxLayoutState.GetPresContext(), &scrollPref);

    if (scrollPref == nsIScrollableFrame::Auto) {
      nscoord vbarwidth, hbarheight;
      scrollFrame->GetScrollbarSizes(aBoxLayoutState.GetPresContext(),
                                     &vbarwidth, &hbarheight);
      aSize.width += vbarwidth;
    }
  }

  AddMargin(child, aSize);
  AddBorderAndPadding(aSize);
  AddInset(aSize);
  nsIBox::AddCSSPrefSize(aBoxLayoutState, this, aSize);
  return rv;

}

nscoord
nsXULTreeOuterGroupFrame::ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState)
{
  if (mStringWidth != -1)
    return mStringWidth;

  nscoord largestWidth = 0;

  nsCOMPtr<nsIContent> firstRowContent;
  nsCOMPtr<nsIContent> content;
  GetTreeContent(getter_AddRefs(content));
  PRInt32 index = 0;
  FindRowContentAtIndex(index, content, getter_AddRefs(firstRowContent));

  if (firstRowContent) {
    nsCOMPtr<nsIStyleContext> styleContext;
    aBoxLayoutState.GetPresContext()->ResolveStyleContextFor(firstRowContent, nsnull,
                                                             PR_FALSE, 
                                                             getter_AddRefs(styleContext));

    nscoord width = 0;
    nsMargin margin(0,0,0,0);

    nsStyleBorderPadding  bPad;
    styleContext->GetBorderPaddingFor(bPad);
    bPad.GetBorderPadding(margin);

    width += (margin.left + margin.right);

    const nsStyleMargin* styleMargin = (const nsStyleMargin*)styleContext->GetStyleData(eStyleStruct_Margin);
    styleMargin->GetMargin(margin);
    width += (margin.left + margin.right);

    nsCOMPtr<nsIContent> content;
    GetTreeContent(getter_AddRefs(content));

    PRInt32 childCount;
    content->ChildCount(childCount);

    nsCOMPtr<nsIContent> child;
    for (PRInt32 i = 0; i < childCount && i < 100; ++i) {
      content->ChildAt(i, *getter_AddRefs(child));

      nsCOMPtr<nsIAtom> tag;
      child->GetTag(*getter_AddRefs(tag));
      if (tag == mTreeRowTag) {
        nsIPresContext* presContext = aBoxLayoutState.GetPresContext();
        nsIRenderingContext* rendContext = aBoxLayoutState.GetReflowState()->rendContext;
        if (rendContext) {
          nsAutoString value;
          nsCOMPtr<nsIContent> textChild;
          PRInt32 textCount;
          child->ChildCount(textCount);
          for (PRInt32 j = 0; j < textCount; ++j) {
            child->ChildAt(j, *getter_AddRefs(textChild));
            nsCOMPtr<nsIDOMText> text(do_QueryInterface(textChild));
            if (text) {
              nsAutoString data;
              text->GetData(data);
              value += data;
            }
          }
          const nsStyleFont* font = (const nsStyleFont*)styleContext->GetStyleData(eStyleStruct_Font);
          nsCOMPtr<nsIDeviceContext> dc;
          presContext->GetDeviceContext(getter_AddRefs(dc));
          nsCOMPtr<nsIFontMetrics> fm;
          dc->GetMetricsFor(font->mFont, *getter_AddRefs(fm));
          rendContext->SetFont(fm);

          nscoord textWidth;
          rendContext->GetWidth(value, textWidth);
          textWidth += width;

          if (textWidth > largestWidth) 
            largestWidth = textWidth;
        }
      }
    }
  }

  mStringWidth = largestWidth;
  return mStringWidth;
}
