Back to index

lightning-sunbird  0.9+nobinonly
nsListBoxBodyFrame.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   David W. Hyatt (hyatt@netscape.com) (Original Author)
00024  *   Joe Hewitt (hewitt@netscape.com)
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nsListBoxBodyFrame.h"
00041 
00042 #include "nsListBoxLayout.h"
00043 
00044 #include "nsCOMPtr.h"
00045 #include "nsGridRowGroupLayout.h"
00046 #include "nsISupportsArray.h"
00047 #include "nsIServiceManager.h"
00048 #include "nsXULAtoms.h"
00049 #include "nsHTMLAtoms.h"
00050 #include "nsIContent.h"
00051 #include "nsITextContent.h"
00052 #include "nsINameSpaceManager.h"
00053 #include "nsIDocument.h"
00054 #include "nsIBindingManager.h"
00055 #include "nsIDOMEventReceiver.h"
00056 #include "nsIDOMMouseEvent.h"
00057 #include "nsIDOMElement.h"
00058 #include "nsIDOMNodeList.h"
00059 #include "nsCSSFrameConstructor.h"
00060 #include "nsIScrollableFrame.h"
00061 #include "nsIScrollbarFrame.h"
00062 #include "nsIScrollableView.h"
00063 #include "nsStyleContext.h"
00064 #include "nsIRenderingContext.h"
00065 #include "nsIDeviceContext.h"
00066 #include "nsIFontMetrics.h"
00067 #include "nsITimer.h"
00068 #include "nsAutoPtr.h"
00069 #include "nsStyleSet.h"
00070 #include "nsIDOMNSDocument.h"
00071 #include "nsPIBoxObject.h"
00072 #include "nsINodeInfo.h"
00073 #include "nsLayoutUtils.h"
00074 #include "nsPIListBoxObject.h"
00075 
00077 
00078 /* A mediator used to smooth out scrolling. It works by seeing if 
00079  * we have time to scroll the amount of rows requested. This is determined
00080  * by measuring how long it takes to scroll a row. If we can scroll the 
00081  * rows in time we do so. If not we start a timer and skip the request. We
00082  * do this until the timer finally first because the user has stopped moving
00083  * the mouse. Then do all the queued requests in on shot.
00084  */
00085 
00086 #ifdef XP_MAC
00087 #pragma mark -
00088 #endif
00089 
00090 // the longest amount of time that can go by before the use
00091 // notices it as a delay.
00092 #define USER_TIME_THRESHOLD 150000
00093 
00094 // how long it takes to layout a single row inital value.
00095 // we will time this after we scroll a few rows.
00096 #define TIME_PER_ROW_INITAL  50000
00097 
00098 // if we decide we can't layout the rows in the amount of time. How long
00099 // do we wait before checking again?
00100 #define SMOOTH_INTERVAL 100
00101 
00102 class nsListScrollSmoother : public nsITimerCallback
00103 {
00104 public:
00105   NS_DECL_ISUPPORTS
00106 
00107   nsListScrollSmoother(nsListBoxBodyFrame* aOuter);
00108   virtual ~nsListScrollSmoother();
00109 
00110   // nsITimerCallback
00111   NS_DECL_NSITIMERCALLBACK
00112 
00113   void Start();
00114   void Stop();
00115   PRBool IsRunning();
00116 
00117   nsCOMPtr<nsITimer> mRepeatTimer;
00118   PRBool mDelta;
00119   nsListBoxBodyFrame* mOuter;
00120 }; 
00121 
00122 nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter)
00123 {
00124   mDelta = 0;
00125   mOuter = aOuter;
00126 }
00127 
00128 nsListScrollSmoother::~nsListScrollSmoother()
00129 {
00130   Stop();
00131 }
00132 
00133 NS_IMETHODIMP
00134 nsListScrollSmoother::Notify(nsITimer *timer)
00135 {
00136   Stop();
00137 
00138   NS_ASSERTION(mOuter, "mOuter is null, see bug #68365");
00139   if (!mOuter) return NS_OK;
00140 
00141   // actually do some work.
00142   mOuter->InternalPositionChangedCallback();
00143   return NS_OK;
00144 }
00145 
00146 PRBool
00147 nsListScrollSmoother::IsRunning()
00148 {
00149   return mRepeatTimer ? PR_TRUE : PR_FALSE;
00150 }
00151 
00152 void
00153 nsListScrollSmoother::Start()
00154 {
00155   Stop();
00156   mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1");
00157   mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT);
00158 }
00159 
00160 void
00161 nsListScrollSmoother::Stop()
00162 {
00163   if ( mRepeatTimer ) {
00164     mRepeatTimer->Cancel();
00165     mRepeatTimer = nsnull;
00166   }
00167 }
00168 
00169 NS_IMPL_ISUPPORTS1(nsListScrollSmoother, nsITimerCallback)
00170 
00171 
00172 
00173 nsListBoxBodyFrame::nsListBoxBodyFrame(nsIPresShell* aPresShell, PRBool aIsRoot, nsIBoxLayout* aLayoutManager)
00174   : nsBoxFrame(aPresShell, aIsRoot, aLayoutManager),
00175     mRowCount(-1),
00176     mRowHeight(0),
00177     mRowHeightWasSet(PR_FALSE),
00178     mAvailableHeight(0),
00179     mStringWidth(-1),
00180     mTopFrame(nsnull),
00181     mBottomFrame(nsnull),
00182     mLinkupFrame(nsnull),
00183     mRowsToPrepend(0),
00184     mCurrentIndex(0),
00185     mOldIndex(0),
00186     mScrolling(PR_FALSE),
00187     mAdjustScroll(PR_FALSE),
00188     mYPosition(0),
00189     mScrollSmoother(nsnull),
00190     mTimePerRow(TIME_PER_ROW_INITAL),
00191     mReflowCallbackPosted(PR_FALSE)
00192 {
00193 }
00194 
00195 nsListBoxBodyFrame::~nsListBoxBodyFrame()
00196 {
00197   NS_IF_RELEASE(mScrollSmoother);
00198 
00199 #if USE_TIMER_TO_DELAY_SCROLLING
00200   StopScrollTracking();
00201   mAutoScrollTimer = nsnull;
00202 #endif
00203 
00204 }
00205 
00207 
00208 NS_IMETHODIMP_(nsrefcnt) 
00209 nsListBoxBodyFrame::AddRef(void)
00210 {
00211   return 2;
00212 }
00213 
00214 NS_IMETHODIMP_(nsrefcnt)
00215 nsListBoxBodyFrame::Release(void)
00216 {
00217   return 1;
00218 }
00219 
00220 //
00221 // QueryInterface
00222 //
00223 NS_INTERFACE_MAP_BEGIN(nsListBoxBodyFrame)
00224   NS_INTERFACE_MAP_ENTRY(nsIListBoxObject)
00225   NS_INTERFACE_MAP_ENTRY(nsIScrollbarMediator)
00226   NS_INTERFACE_MAP_ENTRY(nsIReflowCallback)
00227 NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame)
00228 
00229 
00231 
00232 NS_IMETHODIMP
00233 nsListBoxBodyFrame::Init(nsPresContext* aPresContext, nsIContent* aContent,
00234                          nsIFrame* aParent, nsStyleContext* aContext, nsIFrame* aPrevInFlow)
00235 {
00236   nsresult rv = nsBoxFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
00237 
00238   mOnePixel = aPresContext->IntScaledPixelsToTwips(1);
00239   
00240   nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
00241   if (!scrollFrame)
00242     return rv;
00243 
00244   nsIScrollableView* scrollableView = scrollFrame->GetScrollableView();
00245   scrollableView->SetScrollProperties(NS_SCROLL_PROPERTY_ALWAYS_BLIT);
00246 
00247   nsIBox* verticalScrollbar = scrollFrame->GetScrollbarBox(PR_TRUE);
00248   if (!verticalScrollbar) {
00249     NS_ERROR("Unable to install the scrollbar mediator on the listbox widget. You must be using GFX scrollbars.");
00250     return NS_ERROR_FAILURE;
00251   }
00252 
00253   nsCOMPtr<nsIScrollbarFrame> scrollbarFrame(do_QueryInterface(verticalScrollbar));
00254   scrollbarFrame->SetScrollbarMediator(this);
00255 
00256   nsBoxLayoutState boxLayoutState(aPresContext);
00257 
00258   nsCOMPtr<nsIFontMetrics> fm;
00259   aPresContext->DeviceContext()->GetMetricsFor(aContext->GetStyleFont()->mFont,
00260                                                *getter_AddRefs(fm));
00261   fm->GetHeight(mRowHeight);
00262 
00263   return rv;
00264 }
00265 
00266 NS_IMETHODIMP
00267 nsListBoxBodyFrame::Destroy(nsPresContext* aPresContext)
00268 {
00269   // make sure we cancel any posted callbacks.
00270   if (mReflowCallbackPosted)
00271      aPresContext->PresShell()->CancelReflowCallback(this);
00272 
00273   // Make sure we tell our listbox's box object we're being destroyed.
00274   for (nsIFrame *a = mParent; a; a = a->GetParent()) {
00275     nsIContent *content = a->GetContent();
00276     nsIDocument *doc;
00277 
00278     if (content &&
00279         content->GetNodeInfo()->Equals(nsXULAtoms::listbox,
00280                                        kNameSpaceID_XUL) &&
00281         (doc = content->GetDocument())) {
00282       nsCOMPtr<nsIDOMElement> e(do_QueryInterface(content));
00283       nsCOMPtr<nsIDOMNSDocument> nsdoc(do_QueryInterface(doc));
00284 
00285       nsCOMPtr<nsIBoxObject> box;
00286       nsdoc->GetBoxObjectFor(e, getter_AddRefs(box));
00287 
00288       nsCOMPtr<nsPIListBoxObject> piBox = do_QueryInterface(box);
00289       if (piBox) {
00290         piBox->ClearCachedListBoxBody();
00291       }
00292 
00293       break;
00294     }
00295   }
00296 
00297   return nsBoxFrame::Destroy(aPresContext);
00298 }
00299 
00300 NS_IMETHODIMP
00301 nsListBoxBodyFrame::AttributeChanged(nsIContent* aChild,
00302                                      PRInt32 aNameSpaceID,
00303                                      nsIAtom* aAttribute, 
00304                                      PRInt32 aModType)
00305 {
00306   nsresult rv = NS_OK;
00307 
00308   if (aAttribute == nsXULAtoms::rows) {
00309     nsAutoString rows;
00310     mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::rows, rows);
00311     
00312     if (!rows.IsEmpty()) {
00313       PRInt32 dummy;
00314       PRInt32 count = rows.ToInteger(&dummy);
00315       nsPresContext* presContext = GetPresContext();
00316       float t2p;
00317       t2p = presContext->TwipsToPixels();
00318       PRInt32 rowHeight = GetRowHeightTwips();
00319       rowHeight = NSTwipsToIntPixels(rowHeight, t2p);
00320       nsAutoString value;
00321       value.AppendInt(rowHeight*count);
00322       mContent->SetAttr(kNameSpaceID_None, nsXULAtoms::minheight, value, PR_FALSE);
00323 
00324       nsBoxLayoutState state(presContext);
00325       MarkDirty(state);
00326     }
00327   }
00328   else
00329     rv = nsBoxFrame::AttributeChanged(aChild, aNameSpaceID, aAttribute, aModType);
00330 
00331   return rv;
00332  
00333 }
00334 
00336 
00337 NS_IMETHODIMP
00338 nsListBoxBodyFrame::NeedsRecalc()
00339 {
00340   mStringWidth = -1;
00341   return nsBoxFrame::NeedsRecalc();
00342 }
00343 
00345 
00346 NS_IMETHODIMP
00347 nsListBoxBodyFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState)
00348 {
00349   if (mScrolling)
00350     aBoxLayoutState.SetPaintingDisabled(PR_TRUE);
00351 
00352   nsresult rv = nsBoxFrame::DoLayout(aBoxLayoutState);
00353 
00354   if (mScrolling)
00355     aBoxLayoutState.SetPaintingDisabled(PR_FALSE);
00356 
00357   // if we are scrolled and the row height changed
00358   // make sure we are scrolled to a correct index.
00359   if (mAdjustScroll)
00360      PostReflowCallback();
00361 
00362   return rv;
00363 }
00364 
00365 nsSize
00366 nsListBoxBodyFrame::GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState)
00367 {
00368   nsSize result(0, 0);
00369   nsAutoString sizeMode;
00370   GetContent()->GetAttr(kNameSpaceID_None, nsXULAtoms::sizemode, sizeMode);
00371   if (!sizeMode.IsEmpty()) {  
00372     GetPrefSize(aBoxLayoutState, result);
00373     result.height = 0;
00374     nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
00375     if (scrollFrame &&
00376         scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
00377       nsMargin scrollbars =
00378         scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
00379       result.width += scrollbars.left + scrollbars.right;
00380     }
00381   }
00382   return result;
00383 }
00384 
00385 NS_IMETHODIMP
00386 nsListBoxBodyFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize)
00387 {  
00388   nsresult rv = nsBoxFrame::GetPrefSize(aBoxLayoutState, aSize);
00389 
00390   PRInt32 size = GetFixedRowSize();
00391   nsIBox* box = nsnull;
00392   GetChildBox(&box);
00393   if (size > -1)
00394     aSize.height = size*GetRowHeightTwips();
00395    
00396   nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
00397   if (scrollFrame &&
00398       scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
00399     nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
00400     aSize.width += scrollbars.left + scrollbars.right;
00401   }
00402   return rv;
00403 }
00404 
00406 
00407 NS_IMETHODIMP
00408 nsListBoxBodyFrame::PositionChanged(nsISupports* aScrollbar, PRInt32 aOldIndex, PRInt32& aNewIndex)
00409 { 
00410   if (mScrolling)
00411     return NS_OK;
00412 
00413   PRInt32 oldTwipIndex, newTwipIndex;
00414   oldTwipIndex = mCurrentIndex*mRowHeight;
00415   newTwipIndex = (aNewIndex*mOnePixel);
00416   PRInt32 twipDelta = newTwipIndex > oldTwipIndex ? newTwipIndex - oldTwipIndex : oldTwipIndex - newTwipIndex;
00417 
00418   PRInt32 rowDelta = twipDelta / mRowHeight;
00419   PRInt32 remainder = twipDelta % mRowHeight;
00420   if (remainder > (mRowHeight/2))
00421     rowDelta++;
00422 
00423   if (rowDelta == 0)
00424     return NS_OK;
00425 
00426   // update the position to be row based.
00427 
00428   PRInt32 newIndex = newTwipIndex > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta;
00429   //aNewIndex = newIndex*mRowHeight/mOnePixel;
00430 
00431   nsListScrollSmoother* smoother = GetSmoother();
00432 
00433   // if we can't scroll the rows in time then start a timer. We will eat
00434   // events until the user stops moving and the timer stops.
00435   if (smoother->IsRunning() || rowDelta*mTimePerRow > USER_TIME_THRESHOLD) {
00436 
00437      smoother->Stop();
00438 
00439      // Don't flush anything but reflows lest it destroy us
00440      mContent->GetDocument()->FlushPendingNotifications(Flush_OnlyReflow);
00441 
00442      smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta;
00443 
00444      smoother->Start();
00445 
00446      return NS_OK;
00447   }
00448 
00449   smoother->Stop();
00450 
00451   mCurrentIndex = newIndex;
00452   smoother->mDelta = 0;
00453   
00454   if (mCurrentIndex < 0) {
00455     mCurrentIndex = 0;
00456     return NS_OK;
00457   }
00458 
00459   return InternalPositionChanged(newTwipIndex < oldTwipIndex, rowDelta);
00460 }
00461 
00462 NS_IMETHODIMP
00463 nsListBoxBodyFrame::VisibilityChanged(nsISupports* aScrollbar, PRBool aVisible)
00464 {
00465   PRInt32 lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
00466   if (lastPageTopRow < 0)
00467     lastPageTopRow = 0;
00468   PRInt32 delta = mCurrentIndex - lastPageTopRow;
00469   if (delta > 0) {
00470     mCurrentIndex = lastPageTopRow;
00471     InternalPositionChanged(PR_TRUE, delta);
00472   }
00473 
00474   return NS_OK;
00475 }
00476 
00477 NS_IMETHODIMP
00478 nsListBoxBodyFrame::ScrollbarButtonPressed(nsISupports* aScrollbar, PRInt32 aOldIndex, PRInt32 aNewIndex)
00479 {
00480   if (aOldIndex == aNewIndex)
00481     return NS_OK;
00482   if (aNewIndex < aOldIndex)
00483     mCurrentIndex--;
00484   else mCurrentIndex++;
00485   if (mCurrentIndex < 0) {
00486     mCurrentIndex = 0;
00487     return NS_OK;
00488   }
00489   InternalPositionChanged(aNewIndex < aOldIndex, 1);
00490 
00491   return NS_OK;
00492 }
00493 
00495 
00496 NS_IMETHODIMP
00497 nsListBoxBodyFrame::ReflowFinished(nsIPresShell* aPresShell, PRBool* aFlushFlag)
00498 {
00499   // now create or destroy any rows as needed
00500   CreateRows();
00501 
00502   // keep scrollbar in sync
00503   if (mAdjustScroll) {
00504      VerticalScroll(mYPosition);
00505      mAdjustScroll = PR_FALSE;
00506   }
00507 
00508   // if the row height changed then mark everything as a style change. 
00509   // That will dirty the entire listbox
00510   if (mRowHeightWasSet) {
00511      nsBoxLayoutState state(mPresContext);
00512      MarkStyleChange(state);
00513      PRInt32 pos = mCurrentIndex * mRowHeight;
00514      if (mYPosition != pos) 
00515        mAdjustScroll = PR_TRUE;
00516     mRowHeightWasSet = PR_FALSE;
00517   }
00518 
00519   mReflowCallbackPosted = PR_FALSE;
00520   *aFlushFlag = PR_TRUE;
00521 
00522   return NS_OK;
00523 }
00524 
00526 
00527 NS_IMETHODIMP
00528 nsListBoxBodyFrame::GetListboxBody(nsIListBoxObject * *aListboxBody)
00529 {
00530   *aListboxBody = this;
00531   NS_IF_ADDREF(*aListboxBody);
00532   return NS_OK;
00533 }
00534 
00535 NS_IMETHODIMP
00536 nsListBoxBodyFrame::GetRowCount(PRInt32* aResult)
00537 {
00538   *aResult = GetRowCount();
00539   return NS_OK;
00540 }
00541 
00542 NS_IMETHODIMP
00543 nsListBoxBodyFrame::GetNumberOfVisibleRows(PRInt32 *aResult)
00544 {
00545   *aResult= mRowHeight ? GetAvailableHeight() / mRowHeight : 0;
00546   return NS_OK;
00547 }
00548 
00549 NS_IMETHODIMP
00550 nsListBoxBodyFrame::GetIndexOfFirstVisibleRow(PRInt32 *aResult)
00551 {
00552   *aResult = mCurrentIndex;
00553   return NS_OK;
00554 }
00555 
00556 NS_IMETHODIMP
00557 nsListBoxBodyFrame::EnsureIndexIsVisible(PRInt32 aRowIndex)
00558 {
00559   NS_ASSERTION(aRowIndex >= 0, "Ensure row is visible called with a negative number!");
00560   if (aRowIndex < 0)
00561     return NS_ERROR_ILLEGAL_VALUE;
00562 
00563   PRInt32 rows = 0;
00564   if (mRowHeight)
00565     rows = GetAvailableHeight()/mRowHeight;
00566   if (rows <= 0)
00567     rows = 1;
00568   PRInt32 bottomIndex = mCurrentIndex + rows;
00569   
00570   // if row is visible, ignore
00571   if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex)
00572     return NS_OK;
00573 
00574   // Check to be sure we're not scrolling off the bottom of the tree
00575   PRInt32 delta;
00576 
00577   PRBool up = aRowIndex < mCurrentIndex;
00578   if (up) {
00579     delta = mCurrentIndex - aRowIndex;
00580     mCurrentIndex = aRowIndex;
00581   }
00582   else {
00583     // Bring it just into view.
00584     delta = 1 + (aRowIndex-bottomIndex);
00585     mCurrentIndex += delta; 
00586   }
00587 
00588   InternalPositionChanged(up, delta);
00589   return NS_OK;
00590 }
00591 
00592 NS_IMETHODIMP
00593 nsListBoxBodyFrame::ScrollByLines(PRInt32 aNumLines)
00594 {
00595   PRInt32 scrollIndex, visibleRows;
00596   GetIndexOfFirstVisibleRow(&scrollIndex);
00597   GetNumberOfVisibleRows(&visibleRows);
00598 
00599   scrollIndex += aNumLines;
00600   
00601   if (scrollIndex < 0)
00602     scrollIndex = 0;
00603   else {
00604     PRInt32 numRows = GetRowCount();
00605     PRInt32 lastPageTopRow = numRows - visibleRows;
00606     if (scrollIndex > lastPageTopRow)
00607       scrollIndex = lastPageTopRow;
00608   }
00609   
00610   ScrollToIndex(scrollIndex);
00611 
00612   // we have to do a sync update for mac because if we scroll too quickly
00613   // w/out going back to the main event loop we can easily scroll the wrong
00614   // bits and it looks like garbage (bug 63465).
00615     
00616   // I'd use Composite here, but it doesn't always work.
00617   // vm->Composite();
00618   mPresContext->GetViewManager()->ForceUpdate();
00619 
00620   return NS_OK;
00621 }
00622 
00623 // walks the DOM to get the zero-based row index of the content
00624 NS_IMETHODIMP
00625 nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, PRInt32* _retval)
00626 {
00627   if (!aItem)
00628     return NS_ERROR_NULL_POINTER;
00629   
00630   *_retval = 0;
00631   nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem));
00632 
00633   nsIContent* listbox = mContent->GetBindingParent();
00634   NS_ENSURE_STATE(listbox);
00635 
00636   PRUint32 childCount = listbox->GetChildCount();
00637 
00638   for (PRUint32 childIndex = 0; childIndex < childCount; childIndex++) {
00639     nsIContent *child = listbox->GetChildAt(childIndex);
00640 
00641     // we hit a treerow, count it
00642     if (child->Tag() == nsXULAtoms::listitem) {
00643       // is this it?
00644       if (child == itemContent)
00645         return NS_OK;
00646 
00647       ++(*_retval);
00648     }
00649   }
00650 
00651   // not found
00652   return NS_ERROR_FAILURE;
00653 }
00654 
00655 NS_IMETHODIMP
00656 nsListBoxBodyFrame::GetItemAtIndex(PRInt32 aIndex, nsIDOMElement** aItem)
00657 {
00658   *aItem = nsnull;
00659   if (aIndex < 0)
00660     return NS_ERROR_ILLEGAL_VALUE;
00661   
00662   nsIContent* listbox = mContent->GetBindingParent();
00663   NS_ENSURE_STATE(listbox);
00664 
00665   PRUint32 childCount = listbox->GetChildCount();
00666 
00667   PRInt32 itemCount = 0;
00668   for (PRUint32 childIndex = 0; childIndex < childCount; childIndex++) {
00669     nsIContent *child = listbox->GetChildAt(childIndex);
00670 
00671     // we hit a treerow, count it
00672     if (child->Tag() == nsXULAtoms::listitem) {
00673       // is this it?
00674       if (itemCount == aIndex) {
00675         return CallQueryInterface(child, aItem);
00676       }
00677       ++itemCount;
00678     }
00679   }
00680 
00681   // not found
00682   return NS_ERROR_FAILURE;
00683 }
00684 
00686 
00687 PRInt32
00688 nsListBoxBodyFrame::GetRowCount()
00689 {
00690   if (mRowCount < 0)
00691     ComputeTotalRowCount();
00692   return mRowCount;
00693 }
00694 
00695 PRInt32
00696 nsListBoxBodyFrame::GetFixedRowSize()
00697 {
00698   PRInt32 dummy;
00699 
00700   nsAutoString rows;
00701   mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::rows, rows);
00702   if (!rows.IsEmpty())
00703     return rows.ToInteger(&dummy);
00704  
00705   mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::size, rows);
00706 
00707   if (!rows.IsEmpty())
00708     return rows.ToInteger(&dummy);
00709 
00710   return -1;
00711 }
00712 
00713 void
00714 nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight)
00715 { 
00716   if (aRowHeight > mRowHeight) { 
00717     mRowHeight = aRowHeight;
00718     
00719     nsAutoString rows;
00720     mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::rows, rows);
00721     if (rows.IsEmpty())
00722       mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::size, rows);
00723     
00724     if (!rows.IsEmpty()) {
00725       PRInt32 dummy;
00726       PRInt32 count = rows.ToInteger(&dummy);
00727       float t2p;
00728       t2p = mPresContext->TwipsToPixels();
00729       PRInt32 rowHeight = NSTwipsToIntPixels(aRowHeight, t2p);
00730       nsAutoString value;
00731       value.AppendInt(rowHeight*count);
00732       mContent->SetAttr(kNameSpaceID_None, nsXULAtoms::minheight, value, PR_FALSE);
00733     }
00734 
00735     // signal we need to dirty everything 
00736     // and we want to be notified after reflow
00737     // so we can create or destory rows as needed
00738     mRowHeightWasSet = PR_TRUE;
00739     PostReflowCallback();
00740   }
00741 }
00742 
00743 nscoord
00744 nsListBoxBodyFrame::GetAvailableHeight()
00745 {
00746   nsIScrollableFrame* scrollFrame
00747     = nsLayoutUtils::GetScrollableFrameFor(this);
00748   nsIScrollableView* scrollView = scrollFrame->GetScrollableView();
00749   return scrollView->View()->GetBounds().height;
00750 }
00751 
00752 nscoord
00753 nsListBoxBodyFrame::GetYPosition()
00754 {
00755   return mYPosition;
00756 }
00757 
00758 nscoord
00759 nsListBoxBodyFrame::ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState)
00760 {
00761   if (mStringWidth != -1)
00762     return mStringWidth;
00763 
00764   nscoord largestWidth = 0;
00765 
00766   PRInt32 index = 0;
00767   nsCOMPtr<nsIDOMElement> firstRowEl;
00768   GetItemAtIndex(index, getter_AddRefs(firstRowEl));
00769   nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl));
00770 
00771   if (firstRowContent) {
00772     nsRefPtr<nsStyleContext> styleContext;
00773     nsPresContext *presContext = aBoxLayoutState.PresContext();
00774     styleContext = presContext->StyleSet()->
00775       ResolveStyleFor(firstRowContent, nsnull);
00776 
00777     nscoord width = 0;
00778     nsMargin margin(0,0,0,0);
00779 
00780     nsStyleBorderPadding  bPad;
00781     styleContext->GetBorderPaddingFor(bPad);
00782     bPad.GetBorderPadding(margin);
00783 
00784     width += (margin.left + margin.right);
00785 
00786     styleContext->GetStyleMargin()->GetMargin(margin);
00787     width += (margin.left + margin.right);
00788 
00789     nsIContent* listbox = mContent->GetBindingParent();
00790     NS_ENSURE_TRUE(listbox, largestWidth);
00791 
00792     PRUint32 childCount = listbox->GetChildCount();
00793 
00794     for (PRUint32 i = 0; i < childCount && i < 100; ++i) {
00795       nsIContent *child = listbox->GetChildAt(i);
00796 
00797       if (child->Tag() == nsXULAtoms::listitem) {
00798         nsPresContext* presContext = aBoxLayoutState.PresContext();
00799         nsIRenderingContext* rendContext = aBoxLayoutState.GetReflowState()->rendContext;
00800         if (rendContext) {
00801           nsAutoString value;
00802           PRUint32 textCount = child->GetChildCount();
00803           for (PRUint32 j = 0; j < textCount; ++j) {
00804             nsCOMPtr<nsITextContent> text =
00805               do_QueryInterface(child->GetChildAt(j));
00806 
00807             if (text && text->IsContentOfType(nsIContent::eTEXT)) {
00808               text->AppendTextTo(value);
00809             }
00810           }
00811           nsCOMPtr<nsIFontMetrics> fm;
00812           presContext->DeviceContext()->
00813             GetMetricsFor(styleContext->GetStyleFont()->mFont,
00814                           *getter_AddRefs(fm));
00815           rendContext->SetFont(fm);
00816 
00817           nscoord textWidth;
00818           rendContext->GetWidth(value, textWidth);
00819           textWidth += width;
00820 
00821           if (textWidth > largestWidth) 
00822             largestWidth = textWidth;
00823         }
00824       }
00825     }
00826   }
00827 
00828   mStringWidth = largestWidth;
00829   return mStringWidth;
00830 }
00831 
00832 void
00833 nsListBoxBodyFrame::ComputeTotalRowCount()
00834 {
00835   mRowCount = 0;
00836   nsIContent* listbox = mContent->GetBindingParent();
00837   ENSURE_TRUE(listbox);
00838 
00839   PRUint32 childCount = listbox->GetChildCount();
00840   for (PRUint32 i = 0; i < childCount; i++) {
00841     if (listbox->GetChildAt(i)->Tag() == nsXULAtoms::listitem)
00842       ++mRowCount;
00843   }
00844 }
00845 
00846 void
00847 nsListBoxBodyFrame::PostReflowCallback()
00848 {
00849   if (!mReflowCallbackPosted) {
00850     mReflowCallbackPosted = PR_TRUE;
00851     mPresContext->PresShell()->PostReflowCallback(this);
00852   }
00853 }
00854 
00856 
00857 NS_IMETHODIMP
00858 nsListBoxBodyFrame::ScrollToIndex(PRInt32 aRowIndex)
00859 {
00860   if (( aRowIndex < 0 ) || (mRowHeight == 0))
00861     return NS_OK;
00862     
00863   PRInt32 newIndex = aRowIndex;
00864   PRInt32 delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
00865   PRBool up = newIndex < mCurrentIndex;
00866 
00867   // Check to be sure we're not scrolling off the bottom of the tree
00868   PRInt32 lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
00869   if (lastPageTopRow < 0)
00870     lastPageTopRow = 0;
00871 
00872   if (aRowIndex > lastPageTopRow)
00873     return NS_OK;
00874 
00875   mCurrentIndex = newIndex;
00876   InternalPositionChanged(up, delta);
00877 
00878   // This change has to happen immediately.
00879   // Flush any pending reflow commands.
00880   // Don't flush anything but reflows lest it destroy us
00881   mContent->GetDocument()->FlushPendingNotifications(Flush_OnlyReflow);
00882 
00883   return NS_OK;
00884 }
00885 
00886 NS_IMETHODIMP
00887 nsListBoxBodyFrame::InternalPositionChangedCallback()
00888 {
00889   nsListScrollSmoother* smoother = GetSmoother();
00890 
00891   if (smoother->mDelta == 0)
00892     return NS_OK;
00893 
00894   mCurrentIndex += smoother->mDelta;
00895 
00896   if (mCurrentIndex < 0)
00897     mCurrentIndex = 0;
00898 
00899   return InternalPositionChanged(smoother->mDelta < 0, smoother->mDelta < 0 ? -smoother->mDelta : smoother->mDelta);
00900 }
00901 
00902 NS_IMETHODIMP
00903 nsListBoxBodyFrame::InternalPositionChanged(PRBool aUp, PRInt32 aDelta)
00904 {  
00905   if (aDelta == 0)
00906     return NS_OK;
00907 
00908   nsBoxLayoutState state(mPresContext);
00909 
00910   // begin timing how long it takes to scroll a row
00911   PRTime start = PR_Now();
00912 
00913   mContent->GetDocument()->FlushPendingNotifications(Flush_OnlyReflow);
00914 
00915   PRInt32 visibleRows = 0;
00916   if (mRowHeight)
00917          visibleRows = GetAvailableHeight()/mRowHeight;
00918   
00919   if (aDelta < visibleRows) {
00920     PRInt32 loseRows = aDelta;
00921     if (aUp) {
00922       // scrolling up, destroy rows from the bottom downwards
00923       ReverseDestroyRows(loseRows);
00924       mRowsToPrepend += aDelta;
00925       mLinkupFrame = nsnull;
00926     }
00927     else {
00928       // scrolling down, destroy rows from the top upwards
00929       DestroyRows(loseRows);
00930       mRowsToPrepend = 0;
00931     }
00932   }
00933   else {
00934     // We have scrolled so much that all of our current frames will
00935     // go off screen, so blow them all away. Weeee!
00936     nsIFrame *currBox = mFrames.FirstChild();
00937     while (currBox) {
00938       nsIFrame *nextBox = currBox->GetNextSibling();
00939       RemoveChildFrame(state, currBox);
00940       currBox = nextBox;
00941     }
00942   }
00943 
00944   // clear frame markers so that CreateRows will re-create
00945   mTopFrame = mBottomFrame = nsnull; 
00946   
00947   mYPosition = mCurrentIndex*mRowHeight;
00948   mScrolling = PR_TRUE;
00949   MarkDirtyChildren(state);
00950   // Flush calls CreateRows
00951   // XXXbz there has to be a better way to do this than flushing!
00952   mPresContext->PresShell()->FlushPendingNotifications(Flush_OnlyReflow);
00953   mScrolling = PR_FALSE;
00954   
00955   VerticalScroll(mYPosition);
00956 
00957   PRTime end = PR_Now();
00958 
00959   PRTime difTime;
00960   LL_SUB(difTime, end, start);
00961 
00962   PRInt32 newTime;
00963   LL_L2I(newTime, difTime);
00964   newTime /= aDelta;
00965 
00966   // average old and new
00967   mTimePerRow = (newTime + mTimePerRow)/2;
00968   
00969   return NS_OK;
00970 }
00971 
00972 nsListScrollSmoother* 
00973 nsListBoxBodyFrame::GetSmoother()
00974 {
00975   if (!mScrollSmoother) {
00976     mScrollSmoother = new nsListScrollSmoother(this);
00977     NS_ASSERTION(mScrollSmoother, "out of memory");
00978     NS_IF_ADDREF(mScrollSmoother);
00979   }
00980 
00981   return mScrollSmoother;
00982 }
00983 
00984 void
00985 nsListBoxBodyFrame::VerticalScroll(PRInt32 aPosition)
00986 {
00987   nsIScrollableFrame* scrollFrame
00988     = nsLayoutUtils::GetScrollableFrameFor(this);
00989 
00990   nsPoint scrollPosition = scrollFrame->GetScrollPosition();
00991  
00992   scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition), NS_SCROLL_PROPERTY_ALWAYS_BLIT);
00993 
00994   mYPosition = aPosition;
00995 }
00996 
00998 
00999 nsIFrame*
01000 nsListBoxBodyFrame::GetFirstFrame()
01001 {
01002   mTopFrame = mFrames.FirstChild();
01003   return mTopFrame;
01004 }
01005 
01006 nsIFrame*
01007 nsListBoxBodyFrame::GetLastFrame()
01008 {
01009   return mFrames.LastChild();
01010 }
01011 
01013 
01014 void
01015 nsListBoxBodyFrame::CreateRows()
01016 {
01017   // Get our client rect.
01018   nsRect clientRect;
01019   GetClientRect(clientRect);
01020 
01021   // Get the starting y position and the remaining available
01022   // height.
01023   nscoord availableHeight = GetAvailableHeight();
01024   
01025   if (availableHeight <= 0) {
01026     PRBool fixed = (GetFixedRowSize() != -1);
01027     if (fixed)
01028       availableHeight = 10;
01029     else
01030       return;
01031   }
01032   
01033   // get the first tree box. If there isn't one create one.
01034   PRBool created = PR_FALSE;
01035   nsIBox* box = GetFirstItemBox(0, &created);
01036   nscoord rowHeight = GetRowHeightTwips();
01037   while (box) {  
01038     if (created && mRowsToPrepend > 0)
01039       --mRowsToPrepend;
01040 
01041     // if the row height is 0 then fail. Wait until someone 
01042     // laid out and sets the row height.
01043     if (rowHeight == 0)
01044         return;
01045      
01046     availableHeight -= rowHeight;
01047     
01048     // should we continue? Is the enought height?
01049     if (!ContinueReflow(availableHeight))
01050       break;
01051 
01052     // get the next tree box. Create one if needed.
01053     box = GetNextItemBox(box, 0, &created);
01054   }
01055 
01056   mRowsToPrepend = 0;
01057   mLinkupFrame = nsnull;
01058 }
01059 
01060 void
01061 nsListBoxBodyFrame::DestroyRows(PRInt32& aRowsToLose) 
01062 {
01063   // We need to destroy frames until our row count has been properly
01064   // reduced.  A reflow will then pick up and create the new frames.
01065   nsIFrame* childFrame = GetFirstFrame();
01066   nsBoxLayoutState state(mPresContext);
01067 
01068   while (childFrame && aRowsToLose > 0) {
01069     --aRowsToLose;
01070 
01071     nsIFrame* nextFrame = childFrame->GetNextSibling();
01072     RemoveChildFrame(state, childFrame);
01073 
01074     mTopFrame = childFrame = nextFrame;
01075   }
01076 
01077   MarkDirtyChildren(state);
01078 }
01079 
01080 void
01081 nsListBoxBodyFrame::ReverseDestroyRows(PRInt32& aRowsToLose) 
01082 {
01083   // We need to destroy frames until our row count has been properly
01084   // reduced.  A reflow will then pick up and create the new frames.
01085   nsIFrame* childFrame = GetLastFrame();
01086   nsBoxLayoutState state(mPresContext);
01087 
01088   while (childFrame && aRowsToLose > 0) {
01089     --aRowsToLose;
01090     
01091     nsIFrame* prevFrame;
01092     prevFrame = mFrames.GetPrevSiblingFor(childFrame);
01093     RemoveChildFrame(state, childFrame);
01094 
01095     mBottomFrame = childFrame = prevFrame;
01096   }
01097 
01098   MarkDirtyChildren(state);
01099 }
01100 
01101 //
01102 // Get the nsIBox for the first visible listitem, and if none exists,
01103 // create one.
01104 //
01105 nsIBox* 
01106 nsListBoxBodyFrame::GetFirstItemBox(PRInt32 aOffset, PRBool* aCreated)
01107 {
01108   if (aCreated)
01109    *aCreated = PR_FALSE;
01110 
01111   // Clear ourselves out.
01112   mBottomFrame = mTopFrame;
01113 
01114   if (mTopFrame) {
01115     return mTopFrame->IsBoxFrame() ? NS_STATIC_CAST(nsIBox*, mTopFrame) : nsnull;
01116   }
01117 
01118   // top frame was cleared out
01119   mTopFrame = GetFirstFrame();
01120   mBottomFrame = mTopFrame;
01121 
01122   if (mTopFrame && mRowsToPrepend <= 0) {
01123     return mTopFrame->IsBoxFrame() ? NS_STATIC_CAST(nsIBox*, mTopFrame) : nsnull;
01124   }
01125 
01126   // At this point, we either have no frames at all, 
01127   // or the user has scrolled upwards, leaving frames
01128   // to be created at the top.  Let's determine which
01129   // content needs a new frame first.
01130 
01131   nsCOMPtr<nsIContent> startContent;
01132   if (mTopFrame && mRowsToPrepend > 0) {
01133     // We need to insert rows before the top frame
01134     nsIContent* topContent = mTopFrame->GetContent();
01135     nsIContent* topParent = topContent->GetParent();
01136     PRInt32 contentIndex = topParent->IndexOf(topContent);
01137     contentIndex -= aOffset;
01138     if (contentIndex < 0)
01139       return nsnull;
01140     startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend);
01141   } else {
01142     // This will be the first item frame we create.  Use the content
01143     // at the current index, which is the first index scrolled into view
01144     GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent));
01145   }
01146 
01147   if (startContent) {  
01148     // Either append the new frame, or prepend it (at index 0)
01149     // XXX check here if frame was even created, it may not have been if
01150     //     display: none was on listitem content
01151     PRBool isAppend = mRowsToPrepend <= 0;
01152     nsIFrame* topFrame = nsnull;
01153     mFrameConstructor->CreateListBoxContent(mPresContext, this, nsnull, startContent,
01154                                             &topFrame, isAppend, PR_FALSE, nsnull);
01155     mTopFrame = topFrame;
01156     if (mTopFrame) {
01157       if (aCreated)
01158         *aCreated = PR_TRUE;
01159 
01160       mBottomFrame = mTopFrame;
01161 
01162       return mTopFrame->IsBoxFrame() ? NS_STATIC_CAST(nsIBox*, mTopFrame) : nsnull;
01163     } else
01164       return GetFirstItemBox(++aOffset, 0);
01165   }
01166 
01167   return nsnull;
01168 }
01169 
01170 //
01171 // Get the nsIBox for the next visible listitem after aBox, and if none
01172 // exists, create one.
01173 //
01174 nsIBox* 
01175 nsListBoxBodyFrame::GetNextItemBox(nsIBox* aBox, PRInt32 aOffset,
01176                                    PRBool* aCreated)
01177 {
01178   if (aCreated)
01179     *aCreated = PR_FALSE;
01180 
01181   nsIFrame* result = aBox->GetNextSibling();
01182 
01183   if (!result || result == mLinkupFrame || mRowsToPrepend > 0) {
01184     // No result found. See if there's a content node that wants a frame.
01185     nsIContent* prevContent = aBox->GetContent();
01186     nsIContent* parentContent = prevContent->GetParent();
01187 
01188     PRInt32 i = parentContent->IndexOf(prevContent);
01189 
01190     PRUint32 childCount = parentContent->GetChildCount();
01191     if (((PRUint32)i + aOffset + 1) < childCount) {
01192       // There is a content node that wants a frame.
01193       nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1);
01194 
01195       // Either append the new frame, or insert it after the current frame
01196       PRBool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0;
01197       nsIFrame* prevFrame = isAppend ? nsnull : aBox;
01198       mFrameConstructor->CreateListBoxContent(mPresContext, this, prevFrame,
01199                                               nextContent, &result, isAppend,
01200                                               PR_FALSE, nsnull);
01201 
01202       if (result) {
01203         if (aCreated)
01204            *aCreated = PR_TRUE;
01205       } else
01206         return GetNextItemBox(aBox, ++aOffset, aCreated);
01207             
01208       mLinkupFrame = nsnull;
01209     }
01210   }
01211 
01212   if (!result)
01213     return nsnull;
01214 
01215   mBottomFrame = result;
01216 
01217   return result->IsBoxFrame() ? result : nsnull;
01218 }
01219 
01220 PRBool
01221 nsListBoxBodyFrame::ContinueReflow(nscoord height) 
01222 { 
01223   if (height <= 0) {
01224     nsIFrame* lastChild = GetLastFrame();
01225     nsIFrame* startingPoint = mBottomFrame;
01226     if (startingPoint == nsnull) {
01227       // We just want to delete everything but the first item.
01228       startingPoint = GetFirstFrame();
01229     }
01230 
01231     if (lastChild != startingPoint) {
01232       // We have some hangers on (probably caused by shrinking the size of the window).
01233       // Nuke them.
01234       nsIFrame* currFrame = startingPoint->GetNextSibling();
01235       nsBoxLayoutState state(mPresContext);
01236 
01237       while (currFrame) {
01238         nsIFrame* nextFrame = currFrame->GetNextSibling();
01239         RemoveChildFrame(state, currFrame);
01240         currFrame = nextFrame;
01241       }
01242 
01243       MarkDirtyChildren(state);
01244     }
01245     return PR_FALSE;
01246   }
01247   else
01248     return PR_TRUE;
01249 }
01250 
01251 NS_IMETHODIMP
01252 nsListBoxBodyFrame::ListBoxAppendFrames(nsIFrame* aFrameList)
01253 {
01254   // append them after
01255   nsBoxLayoutState state(mPresContext);
01256   mFrames.AppendFrames(nsnull, aFrameList);
01257   if (mLayoutManager)
01258     mLayoutManager->ChildrenAppended(this, state, aFrameList);
01259   MarkDirtyChildren(state);
01260   
01261   return NS_OK;
01262 }
01263 
01264 NS_IMETHODIMP
01265 nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame, nsIFrame* aFrameList)
01266 {
01267   // insert the frames to our info list
01268   nsBoxLayoutState state(mPresContext);
01269   mFrames.InsertFrames(nsnull, aPrevFrame, aFrameList);
01270   if (mLayoutManager)
01271     mLayoutManager->ChildrenInserted(this, state, aPrevFrame, aFrameList);
01272   MarkDirtyChildren(state);
01273 
01274   return NS_OK;
01275 }
01276 
01277 // 
01278 // Called by nsCSSFrameConstructor when a new listitem content is inserted.
01279 //
01280 void 
01281 nsListBoxBodyFrame::OnContentInserted(nsPresContext* aPresContext, nsIContent* aChildContent)
01282 {
01283   if (mRowCount >= 0)
01284     ++mRowCount;
01285 
01286   nsIPresShell *shell = aPresContext->PresShell();
01287   // The RDF content builder will build content nodes such that they are all 
01288   // ready when OnContentInserted is first called, meaning the first call
01289   // to CreateRows will create all the frames, but OnContentInserted will
01290   // still be called again for each content node - so we need to make sure
01291   // that the frame for each content node hasn't already been created.
01292   nsIFrame* childFrame = nsnull;
01293   shell->GetPrimaryFrameFor(aChildContent, &childFrame);
01294   if (childFrame)
01295     return;
01296 
01297   PRInt32 siblingIndex;
01298   nsCOMPtr<nsIContent> nextSiblingContent;
01299   GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex);
01300   
01301   // if we're inserting our item before the first visible content,
01302   // then we need to shift all rows down by one
01303   if (siblingIndex >= 0 &&  siblingIndex-1 <= mCurrentIndex) {
01304     mTopFrame = nsnull;
01305     mRowsToPrepend = 1;
01306   } else if (nextSiblingContent) {
01307     // we may be inserting before a frame that is on screen
01308     nsIFrame* nextSiblingFrame = nsnull;
01309     shell->GetPrimaryFrameFor(nextSiblingContent, &nextSiblingFrame);
01310     mLinkupFrame = nextSiblingFrame;
01311   }
01312   
01313   CreateRows();
01314   nsBoxLayoutState state(aPresContext);
01315   MarkDirtyChildren(state);
01316 }
01317 
01318 // 
01319 // Called by nsCSSFrameConstructor when listitem content is removed.
01320 //
01321 void
01322 nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext, nsIFrame* aChildFrame, PRInt32 aIndex)
01323 {
01324   NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this,
01325                "Removing frame that's not our child... Not good");
01326   
01327   if (mRowCount >= 0)
01328     --mRowCount;
01329 
01330   nsIContent* listBoxContent = mContent->GetBindingParent();
01331   if (listBoxContent) {
01332     if (!aChildFrame) {
01333       // The row we are removing is out of view, so we need to try to
01334       // determine the index of its next sibling.
01335       nsIContent *oldNextSiblingContent = listBoxContent->GetChildAt(aIndex);
01336   
01337       PRInt32 siblingIndex = -1;
01338       if (oldNextSiblingContent) {
01339         nsCOMPtr<nsIContent> nextSiblingContent;
01340         GetListItemNextSibling(oldNextSiblingContent, getter_AddRefs(nextSiblingContent), siblingIndex);
01341       }
01342     
01343       // if the row being removed is off-screen and above the top frame, we need to
01344       // adjust our top index and tell the scrollbar to shift up one row.
01345       if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) {
01346         NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0");
01347         --mCurrentIndex;
01348         mYPosition = mCurrentIndex*mRowHeight;
01349         VerticalScroll(mYPosition);
01350       }
01351     } else if (mCurrentIndex > 0) {
01352       // At this point, we know we have a scrollbar, and we need to know 
01353       // if we are scrolled to the last row.  In this case, the behavior
01354       // of the scrollbar is to stay locked to the bottom.  Since we are
01355       // removing visible content, the first visible row will have to move
01356       // down by one, and we will have to insert a new frame at the top.
01357       
01358       // if the last content node has a frame, we are scrolled to the bottom
01359       PRUint32 childCount = listBoxContent->GetChildCount();
01360       if (childCount > 0) {
01361         nsIContent *lastChild = listBoxContent->GetChildAt(childCount - 1);
01362         nsIFrame* lastChildFrame = nsnull;
01363         aPresContext->PresShell()->GetPrimaryFrameFor(lastChild,
01364                                                       &lastChildFrame);
01365       
01366         if (lastChildFrame) {
01367           mTopFrame = nsnull;
01368           mRowsToPrepend = 1;
01369           --mCurrentIndex;
01370           mYPosition = mCurrentIndex*mRowHeight;
01371           VerticalScroll(mYPosition);
01372         }
01373       }
01374     }
01375   }
01376 
01377   // if we're removing the top row, the new top row is the next row
01378   if (mTopFrame && mTopFrame == aChildFrame)
01379     mTopFrame = mTopFrame->GetNextSibling();
01380 
01381   // Go ahead and delete the frame.
01382   nsBoxLayoutState state(aPresContext);
01383   if (aChildFrame) {
01384     RemoveChildFrame(state, aChildFrame);
01385   }
01386 
01387   MarkDirtyChildren(state);
01388 }
01389 
01390 void
01391 nsListBoxBodyFrame::GetListItemContentAt(PRInt32 aIndex, nsIContent** aContent)
01392 {
01393   *aContent = nsnull;
01394   nsIContent* listboxContent = mContent->GetBindingParent();
01395   ENSURE_TRUE(listboxContent);
01396 
01397   PRUint32 childCount = listboxContent->GetChildCount();
01398   PRInt32 itemsFound = 0;
01399   for (PRUint32 i = 0; i < childCount; ++i) {
01400     nsIContent *kid = listboxContent->GetChildAt(i);
01401 
01402     if (kid->Tag() == nsXULAtoms::listitem) {
01403       ++itemsFound;
01404       if (itemsFound-1 == aIndex) {
01405         *aContent = kid;
01406         NS_IF_ADDREF(*aContent);
01407         return;
01408       }
01409         
01410     }
01411   }
01412 }
01413 
01414 void
01415 nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, PRInt32& aSiblingIndex)
01416 {
01417   *aContent = nsnull;
01418   aSiblingIndex = -1;
01419   nsIContent* listboxContent = mContent->GetBindingParent();
01420   ENSURE_TRUE(listboxContent);
01421 
01422   PRUint32 childCount = listboxContent->GetChildCount();
01423   nsIContent *prevKid = nsnull;
01424   for (PRUint32 i = 0; i < childCount; ++i) {
01425     nsIContent *kid = listboxContent->GetChildAt(i);
01426 
01427     if (kid->Tag() == nsXULAtoms::listitem) {
01428       ++aSiblingIndex;
01429       if (prevKid == aListItem) {
01430         *aContent = kid;
01431         NS_IF_ADDREF(*aContent);
01432         return;
01433       }
01434         
01435     }
01436     prevKid = kid;
01437   }
01438 
01439   aSiblingIndex = -1; // no match, so there is no next sibling
01440 }
01441 
01442 void
01443 nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState,
01444                                      nsIFrame         *aFrame)
01445 {
01446   mFrameConstructor->RemoveMappingsForFrameSubtree(aFrame, nsnull);
01447 
01448 #ifdef DEBUG
01449   PRBool removed =
01450 #endif
01451     mFrames.RemoveFrame(aFrame);
01452   NS_ASSERTION(removed,
01453                "Going to destroy a frame we didn't remove.  Prepare to crash");
01454   if (mLayoutManager)
01455     mLayoutManager->ChildrenRemoved(this, aState, aFrame);
01456   aFrame->Destroy(mPresContext);
01457 }
01458 
01459 // Creation Routines ///////////////////////////////////////////////////////////////////////
01460 
01461 nsresult
01462 NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRBool aIsRoot, 
01463                        nsIBoxLayout* aLayoutManager)
01464 {
01465   NS_PRECONDITION(aNewFrame, "null OUT ptr");
01466   if (nsnull == aNewFrame) {
01467     return NS_ERROR_NULL_POINTER;
01468   }
01469   nsListBoxBodyFrame* it = new (aPresShell) nsListBoxBodyFrame(aPresShell, aIsRoot, aLayoutManager);
01470   if (!it)
01471     return NS_ERROR_OUT_OF_MEMORY;
01472 
01473   *aNewFrame = it;
01474   return NS_OK;
01475   
01476 }