1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * David W. Hyatt (hyatt@netscape.com) (Original Author)
24 : * Joe Hewitt (hewitt@netscape.com)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsListBoxBodyFrame.h"
41 :
42 : #include "nsListBoxLayout.h"
43 :
44 : #include "nsCOMPtr.h"
45 : #include "nsGridRowGroupLayout.h"
46 : #include "nsIServiceManager.h"
47 : #include "nsGkAtoms.h"
48 : #include "nsIContent.h"
49 : #include "nsINameSpaceManager.h"
50 : #include "nsIDocument.h"
51 : #include "nsIDOMEventTarget.h"
52 : #include "nsIDOMMouseEvent.h"
53 : #include "nsIDOMElement.h"
54 : #include "nsIDOMNodeList.h"
55 : #include "nsCSSFrameConstructor.h"
56 : #include "nsIScrollableFrame.h"
57 : #include "nsScrollbarFrame.h"
58 : #include "nsIView.h"
59 : #include "nsIViewManager.h"
60 : #include "nsStyleContext.h"
61 : #include "nsFontMetrics.h"
62 : #include "nsITimer.h"
63 : #include "nsAutoPtr.h"
64 : #include "nsStyleSet.h"
65 : #include "nsPIBoxObject.h"
66 : #include "nsINodeInfo.h"
67 : #include "nsLayoutUtils.h"
68 : #include "nsPIListBoxObject.h"
69 : #include "nsContentUtils.h"
70 : #include "nsChildIterator.h"
71 : #include "nsRenderingContext.h"
72 :
73 : #ifdef ACCESSIBILITY
74 : #include "nsAccessibilityService.h"
75 : #endif
76 :
77 : /////////////// nsListScrollSmoother //////////////////
78 :
79 : /* A mediator used to smooth out scrolling. It works by seeing if
80 : * we have time to scroll the amount of rows requested. This is determined
81 : * by measuring how long it takes to scroll a row. If we can scroll the
82 : * rows in time we do so. If not we start a timer and skip the request. We
83 : * do this until the timer finally first because the user has stopped moving
84 : * the mouse. Then do all the queued requests in on shot.
85 : */
86 :
87 : // the longest amount of time that can go by before the use
88 : // notices it as a delay.
89 : #define USER_TIME_THRESHOLD 150000
90 :
91 : // how long it takes to layout a single row initial value.
92 : // we will time this after we scroll a few rows.
93 : #define TIME_PER_ROW_INITAL 50000
94 :
95 : // if we decide we can't layout the rows in the amount of time. How long
96 : // do we wait before checking again?
97 : #define SMOOTH_INTERVAL 100
98 :
99 : class nsListScrollSmoother : public nsITimerCallback
100 : {
101 : public:
102 : NS_DECL_ISUPPORTS
103 :
104 : nsListScrollSmoother(nsListBoxBodyFrame* aOuter);
105 : virtual ~nsListScrollSmoother();
106 :
107 : // nsITimerCallback
108 : NS_DECL_NSITIMERCALLBACK
109 :
110 : void Start();
111 : void Stop();
112 : bool IsRunning();
113 :
114 : nsCOMPtr<nsITimer> mRepeatTimer;
115 : PRInt32 mDelta;
116 : nsListBoxBodyFrame* mOuter;
117 : };
118 :
119 0 : nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter)
120 : {
121 0 : mDelta = 0;
122 0 : mOuter = aOuter;
123 0 : }
124 :
125 0 : nsListScrollSmoother::~nsListScrollSmoother()
126 : {
127 0 : Stop();
128 0 : }
129 :
130 : NS_IMETHODIMP
131 0 : nsListScrollSmoother::Notify(nsITimer *timer)
132 : {
133 0 : Stop();
134 :
135 0 : NS_ASSERTION(mOuter, "mOuter is null, see bug #68365");
136 0 : if (!mOuter) return NS_OK;
137 :
138 : // actually do some work.
139 0 : mOuter->InternalPositionChangedCallback();
140 0 : return NS_OK;
141 : }
142 :
143 : bool
144 0 : nsListScrollSmoother::IsRunning()
145 : {
146 0 : return mRepeatTimer ? true : false;
147 : }
148 :
149 : void
150 0 : nsListScrollSmoother::Start()
151 : {
152 0 : Stop();
153 0 : mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1");
154 0 : mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT);
155 0 : }
156 :
157 : void
158 0 : nsListScrollSmoother::Stop()
159 : {
160 0 : if ( mRepeatTimer ) {
161 0 : mRepeatTimer->Cancel();
162 0 : mRepeatTimer = nsnull;
163 : }
164 0 : }
165 :
166 0 : NS_IMPL_ISUPPORTS1(nsListScrollSmoother, nsITimerCallback)
167 :
168 : /////////////// nsListBoxBodyFrame //////////////////
169 :
170 0 : nsListBoxBodyFrame::nsListBoxBodyFrame(nsIPresShell* aPresShell,
171 : nsStyleContext* aContext,
172 : nsBoxLayout* aLayoutManager)
173 : : nsBoxFrame(aPresShell, aContext, false, aLayoutManager),
174 : mTopFrame(nsnull),
175 : mBottomFrame(nsnull),
176 : mLinkupFrame(nsnull),
177 : mScrollSmoother(nsnull),
178 : mRowsToPrepend(0),
179 : mRowCount(-1),
180 : mRowHeight(0),
181 : mAvailableHeight(0),
182 : mStringWidth(-1),
183 : mCurrentIndex(0),
184 : mOldIndex(0),
185 : mYPosition(0),
186 : mTimePerRow(TIME_PER_ROW_INITAL),
187 : mRowHeightWasSet(false),
188 : mScrolling(false),
189 : mAdjustScroll(false),
190 0 : mReflowCallbackPosted(false)
191 : {
192 0 : }
193 :
194 0 : nsListBoxBodyFrame::~nsListBoxBodyFrame()
195 : {
196 0 : NS_IF_RELEASE(mScrollSmoother);
197 :
198 : #if USE_TIMER_TO_DELAY_SCROLLING
199 : StopScrollTracking();
200 : mAutoScrollTimer = nsnull;
201 : #endif
202 :
203 0 : }
204 :
205 0 : NS_QUERYFRAME_HEAD(nsListBoxBodyFrame)
206 0 : NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
207 0 : NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame)
208 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
209 :
210 : ////////// nsIFrame /////////////////
211 :
212 : NS_IMETHODIMP
213 0 : nsListBoxBodyFrame::Init(nsIContent* aContent,
214 : nsIFrame* aParent,
215 : nsIFrame* aPrevInFlow)
216 : {
217 0 : nsresult rv = nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
218 0 : NS_ENSURE_SUCCESS(rv, rv);
219 0 : nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
220 0 : if (scrollFrame) {
221 0 : nsIBox* verticalScrollbar = scrollFrame->GetScrollbarBox(true);
222 0 : nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar);
223 0 : if (scrollbarFrame) {
224 0 : scrollbarFrame->SetScrollbarMediatorContent(GetContent());
225 : }
226 : }
227 0 : nsRefPtr<nsFontMetrics> fm;
228 0 : nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
229 0 : mRowHeight = fm->MaxHeight();
230 :
231 0 : return rv;
232 : }
233 :
234 : void
235 0 : nsListBoxBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
236 : {
237 : // make sure we cancel any posted callbacks.
238 0 : if (mReflowCallbackPosted)
239 0 : PresContext()->PresShell()->CancelReflowCallback(this);
240 :
241 : // Revoke any pending position changed events
242 0 : for (PRUint32 i = 0; i < mPendingPositionChangeEvents.Length(); ++i) {
243 0 : mPendingPositionChangeEvents[i]->Revoke();
244 : }
245 :
246 : // Make sure we tell our listbox's box object we're being destroyed.
247 0 : if (mBoxObject) {
248 0 : mBoxObject->ClearCachedValues();
249 : }
250 :
251 0 : nsBoxFrame::DestroyFrom(aDestructRoot);
252 0 : }
253 :
254 : NS_IMETHODIMP
255 0 : nsListBoxBodyFrame::AttributeChanged(PRInt32 aNameSpaceID,
256 : nsIAtom* aAttribute,
257 : PRInt32 aModType)
258 : {
259 0 : nsresult rv = NS_OK;
260 :
261 0 : if (aAttribute == nsGkAtoms::rows) {
262 0 : PresContext()->PresShell()->
263 0 : FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
264 : }
265 : else
266 0 : rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
267 :
268 0 : return rv;
269 :
270 : }
271 :
272 : /////////// nsIBox ///////////////
273 :
274 : /* virtual */ void
275 0 : nsListBoxBodyFrame::MarkIntrinsicWidthsDirty()
276 : {
277 0 : mStringWidth = -1;
278 0 : nsBoxFrame::MarkIntrinsicWidthsDirty();
279 0 : }
280 :
281 : /////////// nsBox ///////////////
282 :
283 : NS_IMETHODIMP
284 0 : nsListBoxBodyFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState)
285 : {
286 0 : if (mScrolling)
287 0 : aBoxLayoutState.SetPaintingDisabled(true);
288 :
289 0 : nsresult rv = nsBoxFrame::DoLayout(aBoxLayoutState);
290 :
291 : // determine the real height for the scrollable area from the total number
292 : // of rows, since non-visible rows don't yet have frames
293 0 : nsRect rect(nsPoint(0, 0), GetSize());
294 0 : nsOverflowAreas overflow(rect, rect);
295 0 : if (mLayoutManager) {
296 0 : nsIFrame* childFrame = mFrames.FirstChild();
297 0 : while (childFrame) {
298 0 : ConsiderChildOverflow(overflow, childFrame);
299 0 : childFrame = childFrame->GetNextSibling();
300 : }
301 :
302 0 : nsSize prefSize = mLayoutManager->GetPrefSize(this, aBoxLayoutState);
303 0 : NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
304 0 : nsRect& o = overflow.Overflow(otype);
305 0 : o.height = NS_MAX(o.height, prefSize.height);
306 : }
307 : }
308 0 : FinishAndStoreOverflow(overflow, GetSize());
309 :
310 0 : if (mScrolling)
311 0 : aBoxLayoutState.SetPaintingDisabled(false);
312 :
313 : // if we are scrolled and the row height changed
314 : // make sure we are scrolled to a correct index.
315 0 : if (mAdjustScroll)
316 0 : PostReflowCallback();
317 :
318 0 : return rv;
319 : }
320 :
321 : nsSize
322 0 : nsListBoxBodyFrame::GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState)
323 : {
324 0 : nsSize result(0, 0);
325 0 : if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None,
326 0 : nsGkAtoms::sizemode)) {
327 0 : result = GetPrefSize(aBoxLayoutState);
328 0 : result.height = 0;
329 0 : nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
330 0 : if (scrollFrame &&
331 0 : scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
332 : nsMargin scrollbars =
333 0 : scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
334 0 : result.width += scrollbars.left + scrollbars.right;
335 : }
336 : }
337 : return result;
338 : }
339 :
340 : nsSize
341 0 : nsListBoxBodyFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState)
342 : {
343 0 : nsSize pref = nsBoxFrame::GetPrefSize(aBoxLayoutState);
344 :
345 0 : PRInt32 size = GetFixedRowSize();
346 0 : if (size > -1)
347 0 : pref.height = size*GetRowHeightAppUnits();
348 :
349 0 : nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
350 0 : if (scrollFrame &&
351 0 : scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
352 0 : nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
353 0 : pref.width += scrollbars.left + scrollbars.right;
354 : }
355 : return pref;
356 : }
357 :
358 : ///////////// nsIScrollbarMediator ///////////////
359 :
360 : NS_IMETHODIMP
361 0 : nsListBoxBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, PRInt32 aOldIndex, PRInt32& aNewIndex)
362 : {
363 0 : if (mScrolling || mRowHeight == 0)
364 0 : return NS_OK;
365 :
366 : nscoord oldTwipIndex, newTwipIndex;
367 0 : oldTwipIndex = mCurrentIndex*mRowHeight;
368 0 : newTwipIndex = nsPresContext::CSSPixelsToAppUnits(aNewIndex);
369 0 : PRInt32 twipDelta = newTwipIndex > oldTwipIndex ? newTwipIndex - oldTwipIndex : oldTwipIndex - newTwipIndex;
370 :
371 0 : PRInt32 rowDelta = twipDelta / mRowHeight;
372 0 : PRInt32 remainder = twipDelta % mRowHeight;
373 0 : if (remainder > (mRowHeight/2))
374 0 : rowDelta++;
375 :
376 0 : if (rowDelta == 0)
377 0 : return NS_OK;
378 :
379 : // update the position to be row based.
380 :
381 0 : PRInt32 newIndex = newTwipIndex > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta;
382 : //aNewIndex = newIndex*mRowHeight/mOnePixel;
383 :
384 0 : nsListScrollSmoother* smoother = GetSmoother();
385 :
386 : // if we can't scroll the rows in time then start a timer. We will eat
387 : // events until the user stops moving and the timer stops.
388 0 : if (smoother->IsRunning() || rowDelta*mTimePerRow > USER_TIME_THRESHOLD) {
389 :
390 0 : smoother->Stop();
391 :
392 0 : smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta;
393 :
394 0 : smoother->Start();
395 :
396 0 : return NS_OK;
397 : }
398 :
399 0 : smoother->Stop();
400 :
401 0 : mCurrentIndex = newIndex;
402 0 : smoother->mDelta = 0;
403 :
404 0 : if (mCurrentIndex < 0) {
405 0 : mCurrentIndex = 0;
406 0 : return NS_OK;
407 : }
408 :
409 0 : return InternalPositionChanged(newTwipIndex < oldTwipIndex, rowDelta);
410 : }
411 :
412 : NS_IMETHODIMP
413 0 : nsListBoxBodyFrame::VisibilityChanged(bool aVisible)
414 : {
415 0 : if (mRowHeight == 0)
416 0 : return NS_OK;
417 :
418 0 : PRInt32 lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
419 0 : if (lastPageTopRow < 0)
420 0 : lastPageTopRow = 0;
421 0 : PRInt32 delta = mCurrentIndex - lastPageTopRow;
422 0 : if (delta > 0) {
423 0 : mCurrentIndex = lastPageTopRow;
424 0 : InternalPositionChanged(true, delta);
425 : }
426 :
427 0 : return NS_OK;
428 : }
429 :
430 : NS_IMETHODIMP
431 0 : nsListBoxBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, PRInt32 aOldIndex, PRInt32 aNewIndex)
432 : {
433 0 : if (aOldIndex == aNewIndex)
434 0 : return NS_OK;
435 0 : if (aNewIndex < aOldIndex)
436 0 : mCurrentIndex--;
437 0 : else mCurrentIndex++;
438 0 : if (mCurrentIndex < 0) {
439 0 : mCurrentIndex = 0;
440 0 : return NS_OK;
441 : }
442 0 : InternalPositionChanged(aNewIndex < aOldIndex, 1);
443 :
444 0 : return NS_OK;
445 : }
446 :
447 : ///////////// nsIReflowCallback ///////////////
448 :
449 : bool
450 0 : nsListBoxBodyFrame::ReflowFinished()
451 : {
452 0 : nsAutoScriptBlocker scriptBlocker;
453 : // now create or destroy any rows as needed
454 0 : CreateRows();
455 :
456 : // keep scrollbar in sync
457 0 : if (mAdjustScroll) {
458 0 : VerticalScroll(mYPosition);
459 0 : mAdjustScroll = false;
460 : }
461 :
462 : // if the row height changed then mark everything as a style change.
463 : // That will dirty the entire listbox
464 0 : if (mRowHeightWasSet) {
465 0 : PresContext()->PresShell()->
466 0 : FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
467 0 : PRInt32 pos = mCurrentIndex * mRowHeight;
468 0 : if (mYPosition != pos)
469 0 : mAdjustScroll = true;
470 0 : mRowHeightWasSet = false;
471 : }
472 :
473 0 : mReflowCallbackPosted = false;
474 0 : return true;
475 : }
476 :
477 : void
478 0 : nsListBoxBodyFrame::ReflowCallbackCanceled()
479 : {
480 0 : mReflowCallbackPosted = false;
481 0 : }
482 :
483 : ///////// nsIListBoxObject ///////////////
484 :
485 : nsresult
486 0 : nsListBoxBodyFrame::GetRowCount(PRInt32* aResult)
487 : {
488 0 : *aResult = GetRowCount();
489 0 : return NS_OK;
490 : }
491 :
492 : nsresult
493 0 : nsListBoxBodyFrame::GetNumberOfVisibleRows(PRInt32 *aResult)
494 : {
495 0 : *aResult= mRowHeight ? GetAvailableHeight() / mRowHeight : 0;
496 0 : return NS_OK;
497 : }
498 :
499 : nsresult
500 0 : nsListBoxBodyFrame::GetIndexOfFirstVisibleRow(PRInt32 *aResult)
501 : {
502 0 : *aResult = mCurrentIndex;
503 0 : return NS_OK;
504 : }
505 :
506 : nsresult
507 0 : nsListBoxBodyFrame::EnsureIndexIsVisible(PRInt32 aRowIndex)
508 : {
509 0 : if (aRowIndex < 0)
510 0 : return NS_ERROR_ILLEGAL_VALUE;
511 :
512 0 : PRInt32 rows = 0;
513 0 : if (mRowHeight)
514 0 : rows = GetAvailableHeight()/mRowHeight;
515 0 : if (rows <= 0)
516 0 : rows = 1;
517 0 : PRInt32 bottomIndex = mCurrentIndex + rows;
518 :
519 : // if row is visible, ignore
520 0 : if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex)
521 0 : return NS_OK;
522 :
523 : PRInt32 delta;
524 :
525 0 : bool up = aRowIndex < mCurrentIndex;
526 0 : if (up) {
527 0 : delta = mCurrentIndex - aRowIndex;
528 0 : mCurrentIndex = aRowIndex;
529 : }
530 : else {
531 : // Check to be sure we're not scrolling off the bottom of the tree
532 0 : if (aRowIndex >= GetRowCount())
533 0 : return NS_ERROR_ILLEGAL_VALUE;
534 :
535 : // Bring it just into view.
536 0 : delta = 1 + (aRowIndex-bottomIndex);
537 0 : mCurrentIndex += delta;
538 : }
539 :
540 : // Safe to not go off an event here, since this is coming from the
541 : // box object.
542 0 : DoInternalPositionChangedSync(up, delta);
543 0 : return NS_OK;
544 : }
545 :
546 : nsresult
547 0 : nsListBoxBodyFrame::ScrollByLines(PRInt32 aNumLines)
548 : {
549 : PRInt32 scrollIndex, visibleRows;
550 0 : GetIndexOfFirstVisibleRow(&scrollIndex);
551 0 : GetNumberOfVisibleRows(&visibleRows);
552 :
553 0 : scrollIndex += aNumLines;
554 :
555 0 : if (scrollIndex < 0)
556 0 : scrollIndex = 0;
557 : else {
558 0 : PRInt32 numRows = GetRowCount();
559 0 : PRInt32 lastPageTopRow = numRows - visibleRows;
560 0 : if (scrollIndex > lastPageTopRow)
561 0 : scrollIndex = lastPageTopRow;
562 : }
563 :
564 0 : ScrollToIndex(scrollIndex);
565 :
566 0 : return NS_OK;
567 : }
568 :
569 : // walks the DOM to get the zero-based row index of the content
570 : nsresult
571 0 : nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, PRInt32* _retval)
572 : {
573 0 : if (aItem) {
574 0 : *_retval = 0;
575 0 : nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem));
576 :
577 0 : ChildIterator iter, last;
578 0 : for (ChildIterator::Init(mContent, &iter, &last);
579 : iter != last;
580 : ++iter) {
581 0 : nsIContent *child = (*iter);
582 : // we hit a list row, count it
583 0 : if (child->Tag() == nsGkAtoms::listitem) {
584 : // is this it?
585 0 : if (child == itemContent)
586 0 : return NS_OK;
587 :
588 0 : ++(*_retval);
589 : }
590 : }
591 : }
592 :
593 : // not found
594 0 : *_retval = -1;
595 0 : return NS_OK;
596 : }
597 :
598 : nsresult
599 0 : nsListBoxBodyFrame::GetItemAtIndex(PRInt32 aIndex, nsIDOMElement** aItem)
600 : {
601 0 : *aItem = nsnull;
602 0 : if (aIndex < 0)
603 0 : return NS_OK;
604 :
605 0 : PRInt32 itemCount = 0;
606 0 : ChildIterator iter, last;
607 0 : for (ChildIterator::Init(mContent, &iter, &last);
608 : iter != last;
609 : ++iter) {
610 0 : nsIContent *child = (*iter);
611 : // we hit a list row, check if it is the one we are looking for
612 0 : if (child->Tag() == nsGkAtoms::listitem) {
613 : // is this it?
614 0 : if (itemCount == aIndex) {
615 0 : return CallQueryInterface(child, aItem);
616 : }
617 0 : ++itemCount;
618 : }
619 : }
620 :
621 : // not found
622 0 : return NS_OK;
623 : }
624 :
625 : /////////// nsListBoxBodyFrame ///////////////
626 :
627 : PRInt32
628 0 : nsListBoxBodyFrame::GetRowCount()
629 : {
630 0 : if (mRowCount < 0)
631 0 : ComputeTotalRowCount();
632 0 : return mRowCount;
633 : }
634 :
635 : PRInt32
636 0 : nsListBoxBodyFrame::GetFixedRowSize()
637 : {
638 : PRInt32 dummy;
639 :
640 0 : nsAutoString rows;
641 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
642 0 : if (!rows.IsEmpty())
643 0 : return rows.ToInteger(&dummy);
644 :
645 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::size, rows);
646 :
647 0 : if (!rows.IsEmpty())
648 0 : return rows.ToInteger(&dummy);
649 :
650 0 : return -1;
651 : }
652 :
653 : void
654 0 : nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight)
655 : {
656 0 : if (aRowHeight > mRowHeight) {
657 0 : mRowHeight = aRowHeight;
658 :
659 : // signal we need to dirty everything
660 : // and we want to be notified after reflow
661 : // so we can create or destory rows as needed
662 0 : mRowHeightWasSet = true;
663 0 : PostReflowCallback();
664 : }
665 0 : }
666 :
667 : nscoord
668 0 : nsListBoxBodyFrame::GetAvailableHeight()
669 : {
670 : nsIScrollableFrame* scrollFrame =
671 0 : nsLayoutUtils::GetScrollableFrameFor(this);
672 0 : if (scrollFrame) {
673 0 : return scrollFrame->GetScrollPortRect().height;
674 : }
675 0 : return 0;
676 : }
677 :
678 : nscoord
679 0 : nsListBoxBodyFrame::GetYPosition()
680 : {
681 0 : return mYPosition;
682 : }
683 :
684 : nscoord
685 0 : nsListBoxBodyFrame::ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState)
686 : {
687 0 : if (mStringWidth != -1)
688 0 : return mStringWidth;
689 :
690 0 : nscoord largestWidth = 0;
691 :
692 0 : PRInt32 index = 0;
693 0 : nsCOMPtr<nsIDOMElement> firstRowEl;
694 0 : GetItemAtIndex(index, getter_AddRefs(firstRowEl));
695 0 : nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl));
696 :
697 0 : if (firstRowContent) {
698 0 : nsRefPtr<nsStyleContext> styleContext;
699 0 : nsPresContext *presContext = aBoxLayoutState.PresContext();
700 : styleContext = presContext->StyleSet()->
701 0 : ResolveStyleFor(firstRowContent->AsElement(), nsnull);
702 :
703 0 : nscoord width = 0;
704 0 : nsMargin margin(0,0,0,0);
705 :
706 0 : if (styleContext->GetStylePadding()->GetPadding(margin))
707 0 : width += margin.LeftRight();
708 0 : width += styleContext->GetStyleBorder()->GetActualBorder().LeftRight();
709 0 : if (styleContext->GetStyleMargin()->GetMargin(margin))
710 0 : width += margin.LeftRight();
711 :
712 :
713 0 : ChildIterator iter, last;
714 0 : PRUint32 i = 0;
715 0 : for (ChildIterator::Init(mContent, &iter, &last);
716 0 : iter != last && i < 100;
717 : ++iter, ++i) {
718 0 : nsIContent *child = (*iter);
719 :
720 0 : if (child->Tag() == nsGkAtoms::listitem) {
721 0 : nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
722 0 : if (rendContext) {
723 0 : nsAutoString value;
724 0 : PRUint32 textCount = child->GetChildCount();
725 0 : for (PRUint32 j = 0; j < textCount; ++j) {
726 0 : nsIContent* text = child->GetChildAt(j);
727 0 : if (text && text->IsNodeOfType(nsINode::eTEXT)) {
728 0 : text->AppendTextTo(value);
729 : }
730 : }
731 :
732 0 : nsRefPtr<nsFontMetrics> fm;
733 : nsLayoutUtils::GetFontMetricsForStyleContext(styleContext,
734 0 : getter_AddRefs(fm));
735 0 : rendContext->SetFont(fm);
736 :
737 : nscoord textWidth =
738 0 : nsLayoutUtils::GetStringWidth(this, rendContext, value.get(), value.Length());
739 0 : textWidth += width;
740 :
741 0 : if (textWidth > largestWidth)
742 0 : largestWidth = textWidth;
743 : }
744 : }
745 : }
746 : }
747 :
748 0 : mStringWidth = largestWidth;
749 0 : return mStringWidth;
750 : }
751 :
752 : void
753 0 : nsListBoxBodyFrame::ComputeTotalRowCount()
754 : {
755 0 : mRowCount = 0;
756 :
757 0 : ChildIterator iter, last;
758 0 : for (ChildIterator::Init(mContent, &iter, &last);
759 : iter != last;
760 : ++iter) {
761 0 : if ((*iter)->Tag() == nsGkAtoms::listitem)
762 0 : ++mRowCount;
763 : }
764 0 : }
765 :
766 : void
767 0 : nsListBoxBodyFrame::PostReflowCallback()
768 : {
769 0 : if (!mReflowCallbackPosted) {
770 0 : mReflowCallbackPosted = true;
771 0 : PresContext()->PresShell()->PostReflowCallback(this);
772 : }
773 0 : }
774 :
775 : ////////// scrolling
776 :
777 : nsresult
778 0 : nsListBoxBodyFrame::ScrollToIndex(PRInt32 aRowIndex)
779 : {
780 0 : if (( aRowIndex < 0 ) || (mRowHeight == 0))
781 0 : return NS_OK;
782 :
783 0 : PRInt32 newIndex = aRowIndex;
784 0 : PRInt32 delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
785 0 : bool up = newIndex < mCurrentIndex;
786 :
787 : // Check to be sure we're not scrolling off the bottom of the tree
788 0 : PRInt32 lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
789 0 : if (lastPageTopRow < 0)
790 0 : lastPageTopRow = 0;
791 :
792 0 : if (aRowIndex > lastPageTopRow)
793 0 : return NS_OK;
794 :
795 0 : mCurrentIndex = newIndex;
796 :
797 0 : nsWeakFrame weak(this);
798 :
799 : // Since we're going to flush anyway, we need to not do this off an event
800 0 : DoInternalPositionChangedSync(up, delta);
801 :
802 0 : if (!weak.IsAlive()) {
803 0 : return NS_OK;
804 : }
805 :
806 : // This change has to happen immediately.
807 : // Flush any pending reflow commands.
808 : // XXXbz why, exactly?
809 0 : mContent->GetDocument()->FlushPendingNotifications(Flush_Layout);
810 :
811 0 : return NS_OK;
812 : }
813 :
814 : nsresult
815 0 : nsListBoxBodyFrame::InternalPositionChangedCallback()
816 : {
817 0 : nsListScrollSmoother* smoother = GetSmoother();
818 :
819 0 : if (smoother->mDelta == 0)
820 0 : return NS_OK;
821 :
822 0 : mCurrentIndex += smoother->mDelta;
823 :
824 0 : if (mCurrentIndex < 0)
825 0 : mCurrentIndex = 0;
826 :
827 : return DoInternalPositionChangedSync(smoother->mDelta < 0,
828 : smoother->mDelta < 0 ?
829 0 : -smoother->mDelta : smoother->mDelta);
830 : }
831 :
832 : nsresult
833 0 : nsListBoxBodyFrame::InternalPositionChanged(bool aUp, PRInt32 aDelta)
834 : {
835 : nsRefPtr<nsPositionChangedEvent> ev =
836 0 : new nsPositionChangedEvent(this, aUp, aDelta);
837 0 : nsresult rv = NS_DispatchToCurrentThread(ev);
838 0 : if (NS_SUCCEEDED(rv)) {
839 0 : if (!mPendingPositionChangeEvents.AppendElement(ev)) {
840 0 : rv = NS_ERROR_OUT_OF_MEMORY;
841 0 : ev->Revoke();
842 : }
843 : }
844 0 : return rv;
845 : }
846 :
847 : nsresult
848 0 : nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp, PRInt32 aDelta)
849 : {
850 0 : nsWeakFrame weak(this);
851 :
852 : // Process all the pending position changes first
853 0 : nsTArray< nsRefPtr<nsPositionChangedEvent> > temp;
854 0 : temp.SwapElements(mPendingPositionChangeEvents);
855 0 : for (PRUint32 i = 0; i < temp.Length(); ++i) {
856 0 : if (weak.IsAlive()) {
857 0 : temp[i]->Run();
858 : }
859 0 : temp[i]->Revoke();
860 : }
861 :
862 0 : if (!weak.IsAlive()) {
863 0 : return NS_OK;
864 : }
865 :
866 0 : return DoInternalPositionChanged(aUp, aDelta);
867 : }
868 :
869 : nsresult
870 0 : nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp, PRInt32 aDelta)
871 : {
872 0 : if (aDelta == 0)
873 0 : return NS_OK;
874 :
875 0 : nsRefPtr<nsPresContext> presContext(PresContext());
876 0 : nsBoxLayoutState state(presContext);
877 :
878 : // begin timing how long it takes to scroll a row
879 0 : PRTime start = PR_Now();
880 :
881 0 : nsWeakFrame weakThis(this);
882 0 : mContent->GetDocument()->FlushPendingNotifications(Flush_Layout);
883 0 : if (!weakThis.IsAlive()) {
884 0 : return NS_OK;
885 : }
886 :
887 : {
888 0 : nsAutoScriptBlocker scriptBlocker;
889 :
890 0 : PRInt32 visibleRows = 0;
891 0 : if (mRowHeight)
892 0 : visibleRows = GetAvailableHeight()/mRowHeight;
893 :
894 0 : if (aDelta < visibleRows) {
895 0 : PRInt32 loseRows = aDelta;
896 0 : if (aUp) {
897 : // scrolling up, destroy rows from the bottom downwards
898 0 : ReverseDestroyRows(loseRows);
899 0 : mRowsToPrepend += aDelta;
900 0 : mLinkupFrame = nsnull;
901 : }
902 : else {
903 : // scrolling down, destroy rows from the top upwards
904 0 : DestroyRows(loseRows);
905 0 : mRowsToPrepend = 0;
906 : }
907 : }
908 : else {
909 : // We have scrolled so much that all of our current frames will
910 : // go off screen, so blow them all away. Weeee!
911 0 : nsIFrame *currBox = mFrames.FirstChild();
912 0 : nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
913 0 : fc->BeginUpdate();
914 0 : while (currBox) {
915 0 : nsIFrame *nextBox = currBox->GetNextSibling();
916 0 : RemoveChildFrame(state, currBox);
917 0 : currBox = nextBox;
918 : }
919 0 : fc->EndUpdate();
920 : }
921 :
922 : // clear frame markers so that CreateRows will re-create
923 0 : mTopFrame = mBottomFrame = nsnull;
924 :
925 0 : mYPosition = mCurrentIndex*mRowHeight;
926 0 : mScrolling = true;
927 0 : presContext->PresShell()->
928 0 : FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
929 : }
930 0 : if (!weakThis.IsAlive()) {
931 0 : return NS_OK;
932 : }
933 : // Flush calls CreateRows
934 : // XXXbz there has to be a better way to do this than flushing!
935 0 : presContext->PresShell()->FlushPendingNotifications(Flush_Layout);
936 0 : if (!weakThis.IsAlive()) {
937 0 : return NS_OK;
938 : }
939 :
940 0 : mScrolling = false;
941 :
942 0 : VerticalScroll(mYPosition);
943 :
944 0 : PRTime end = PR_Now();
945 :
946 : PRTime difTime;
947 0 : LL_SUB(difTime, end, start);
948 :
949 : PRInt32 newTime;
950 0 : LL_L2I(newTime, difTime);
951 0 : newTime /= aDelta;
952 :
953 : // average old and new
954 0 : mTimePerRow = (newTime + mTimePerRow)/2;
955 :
956 0 : return NS_OK;
957 : }
958 :
959 : nsListScrollSmoother*
960 0 : nsListBoxBodyFrame::GetSmoother()
961 : {
962 0 : if (!mScrollSmoother) {
963 0 : mScrollSmoother = new nsListScrollSmoother(this);
964 0 : NS_ASSERTION(mScrollSmoother, "out of memory");
965 0 : NS_IF_ADDREF(mScrollSmoother);
966 : }
967 :
968 0 : return mScrollSmoother;
969 : }
970 :
971 : void
972 0 : nsListBoxBodyFrame::VerticalScroll(PRInt32 aPosition)
973 : {
974 : nsIScrollableFrame* scrollFrame
975 0 : = nsLayoutUtils::GetScrollableFrameFor(this);
976 0 : if (!scrollFrame) {
977 0 : return;
978 : }
979 :
980 0 : nsPoint scrollPosition = scrollFrame->GetScrollPosition();
981 :
982 : scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition),
983 0 : nsIScrollableFrame::INSTANT);
984 :
985 0 : mYPosition = aPosition;
986 : }
987 :
988 : ////////// frame and box retrieval
989 :
990 : nsIFrame*
991 0 : nsListBoxBodyFrame::GetFirstFrame()
992 : {
993 0 : mTopFrame = mFrames.FirstChild();
994 0 : return mTopFrame;
995 : }
996 :
997 : nsIFrame*
998 0 : nsListBoxBodyFrame::GetLastFrame()
999 : {
1000 0 : return mFrames.LastChild();
1001 : }
1002 :
1003 : bool
1004 0 : nsListBoxBodyFrame::SupportsOrdinalsInChildren()
1005 : {
1006 0 : return false;
1007 : }
1008 :
1009 : ////////// lazy row creation and destruction
1010 :
1011 : void
1012 0 : nsListBoxBodyFrame::CreateRows()
1013 : {
1014 : // Get our client rect.
1015 0 : nsRect clientRect;
1016 0 : GetClientRect(clientRect);
1017 :
1018 : // Get the starting y position and the remaining available
1019 : // height.
1020 0 : nscoord availableHeight = GetAvailableHeight();
1021 :
1022 0 : if (availableHeight <= 0) {
1023 0 : bool fixed = (GetFixedRowSize() != -1);
1024 0 : if (fixed)
1025 0 : availableHeight = 10;
1026 : else
1027 : return;
1028 : }
1029 :
1030 : // get the first tree box. If there isn't one create one.
1031 0 : bool created = false;
1032 0 : nsIBox* box = GetFirstItemBox(0, &created);
1033 0 : nscoord rowHeight = GetRowHeightAppUnits();
1034 0 : while (box) {
1035 0 : if (created && mRowsToPrepend > 0)
1036 0 : --mRowsToPrepend;
1037 :
1038 : // if the row height is 0 then fail. Wait until someone
1039 : // laid out and sets the row height.
1040 0 : if (rowHeight == 0)
1041 : return;
1042 :
1043 0 : availableHeight -= rowHeight;
1044 :
1045 : // should we continue? Is the enought height?
1046 0 : if (!ContinueReflow(availableHeight))
1047 0 : break;
1048 :
1049 : // get the next tree box. Create one if needed.
1050 0 : box = GetNextItemBox(box, 0, &created);
1051 : }
1052 :
1053 0 : mRowsToPrepend = 0;
1054 0 : mLinkupFrame = nsnull;
1055 : }
1056 :
1057 : void
1058 0 : nsListBoxBodyFrame::DestroyRows(PRInt32& aRowsToLose)
1059 : {
1060 : // We need to destroy frames until our row count has been properly
1061 : // reduced. A reflow will then pick up and create the new frames.
1062 0 : nsIFrame* childFrame = GetFirstFrame();
1063 0 : nsBoxLayoutState state(PresContext());
1064 :
1065 0 : nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
1066 0 : fc->BeginUpdate();
1067 0 : while (childFrame && aRowsToLose > 0) {
1068 0 : --aRowsToLose;
1069 :
1070 0 : nsIFrame* nextFrame = childFrame->GetNextSibling();
1071 0 : RemoveChildFrame(state, childFrame);
1072 :
1073 0 : mTopFrame = childFrame = nextFrame;
1074 : }
1075 0 : fc->EndUpdate();
1076 :
1077 0 : PresContext()->PresShell()->
1078 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1079 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1080 0 : }
1081 :
1082 : void
1083 0 : nsListBoxBodyFrame::ReverseDestroyRows(PRInt32& aRowsToLose)
1084 : {
1085 : // We need to destroy frames until our row count has been properly
1086 : // reduced. A reflow will then pick up and create the new frames.
1087 0 : nsIFrame* childFrame = GetLastFrame();
1088 0 : nsBoxLayoutState state(PresContext());
1089 :
1090 0 : nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
1091 0 : fc->BeginUpdate();
1092 0 : while (childFrame && aRowsToLose > 0) {
1093 0 : --aRowsToLose;
1094 :
1095 : nsIFrame* prevFrame;
1096 0 : prevFrame = childFrame->GetPrevSibling();
1097 0 : RemoveChildFrame(state, childFrame);
1098 :
1099 0 : mBottomFrame = childFrame = prevFrame;
1100 : }
1101 0 : fc->EndUpdate();
1102 :
1103 0 : PresContext()->PresShell()->
1104 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1105 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1106 0 : }
1107 :
1108 : static bool
1109 0 : IsListItemChild(nsListBoxBodyFrame* aParent, nsIContent* aChild,
1110 : nsIFrame** aChildFrame)
1111 : {
1112 0 : *aChildFrame = nsnull;
1113 0 : if (!aChild->IsXUL() || aChild->Tag() != nsGkAtoms::listitem) {
1114 0 : return false;
1115 : }
1116 0 : nsIFrame* existingFrame = aChild->GetPrimaryFrame();
1117 0 : if (existingFrame && existingFrame->GetParent() != aParent) {
1118 0 : return false;
1119 : }
1120 0 : *aChildFrame = existingFrame;
1121 0 : return true;
1122 : }
1123 :
1124 : //
1125 : // Get the nsIBox for the first visible listitem, and if none exists,
1126 : // create one.
1127 : //
1128 : nsIBox*
1129 0 : nsListBoxBodyFrame::GetFirstItemBox(PRInt32 aOffset, bool* aCreated)
1130 : {
1131 0 : if (aCreated)
1132 0 : *aCreated = false;
1133 :
1134 : // Clear ourselves out.
1135 0 : mBottomFrame = mTopFrame;
1136 :
1137 0 : if (mTopFrame) {
1138 0 : return mTopFrame->IsBoxFrame() ? static_cast<nsIBox*>(mTopFrame) : nsnull;
1139 : }
1140 :
1141 : // top frame was cleared out
1142 0 : mTopFrame = GetFirstFrame();
1143 0 : mBottomFrame = mTopFrame;
1144 :
1145 0 : if (mTopFrame && mRowsToPrepend <= 0) {
1146 0 : return mTopFrame->IsBoxFrame() ? static_cast<nsIBox*>(mTopFrame) : nsnull;
1147 : }
1148 :
1149 : // At this point, we either have no frames at all,
1150 : // or the user has scrolled upwards, leaving frames
1151 : // to be created at the top. Let's determine which
1152 : // content needs a new frame first.
1153 :
1154 0 : nsCOMPtr<nsIContent> startContent;
1155 0 : if (mTopFrame && mRowsToPrepend > 0) {
1156 : // We need to insert rows before the top frame
1157 0 : nsIContent* topContent = mTopFrame->GetContent();
1158 0 : nsIContent* topParent = topContent->GetParent();
1159 0 : PRInt32 contentIndex = topParent->IndexOf(topContent);
1160 0 : contentIndex -= aOffset;
1161 0 : if (contentIndex < 0)
1162 0 : return nsnull;
1163 0 : startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend);
1164 : } else {
1165 : // This will be the first item frame we create. Use the content
1166 : // at the current index, which is the first index scrolled into view
1167 0 : GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent));
1168 : }
1169 :
1170 0 : if (startContent) {
1171 : nsIFrame* existingFrame;
1172 0 : if (!IsListItemChild(this, startContent, &existingFrame)) {
1173 0 : return GetFirstItemBox(++aOffset, aCreated);
1174 : }
1175 0 : if (existingFrame) {
1176 0 : return existingFrame->IsBoxFrame() ? existingFrame : nsnull;
1177 : }
1178 :
1179 : // Either append the new frame, or prepend it (at index 0)
1180 : // XXX check here if frame was even created, it may not have been if
1181 : // display: none was on listitem content
1182 0 : bool isAppend = mRowsToPrepend <= 0;
1183 :
1184 0 : nsPresContext* presContext = PresContext();
1185 0 : nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
1186 0 : nsIFrame* topFrame = nsnull;
1187 : fc->CreateListBoxContent(presContext, this, nsnull, startContent,
1188 0 : &topFrame, isAppend, false, nsnull);
1189 0 : mTopFrame = topFrame;
1190 0 : if (mTopFrame) {
1191 0 : if (aCreated)
1192 0 : *aCreated = true;
1193 :
1194 0 : mBottomFrame = mTopFrame;
1195 :
1196 0 : return mTopFrame->IsBoxFrame() ? static_cast<nsIBox*>(mTopFrame) : nsnull;
1197 : } else
1198 0 : return GetFirstItemBox(++aOffset, 0);
1199 : }
1200 :
1201 0 : return nsnull;
1202 : }
1203 :
1204 : //
1205 : // Get the nsIBox for the next visible listitem after aBox, and if none
1206 : // exists, create one.
1207 : //
1208 : nsIBox*
1209 0 : nsListBoxBodyFrame::GetNextItemBox(nsIBox* aBox, PRInt32 aOffset,
1210 : bool* aCreated)
1211 : {
1212 0 : if (aCreated)
1213 0 : *aCreated = false;
1214 :
1215 0 : nsIFrame* result = aBox->GetNextSibling();
1216 :
1217 0 : if (!result || result == mLinkupFrame || mRowsToPrepend > 0) {
1218 : // No result found. See if there's a content node that wants a frame.
1219 0 : nsIContent* prevContent = aBox->GetContent();
1220 0 : nsIContent* parentContent = prevContent->GetParent();
1221 :
1222 0 : PRInt32 i = parentContent->IndexOf(prevContent);
1223 :
1224 0 : PRUint32 childCount = parentContent->GetChildCount();
1225 0 : if (((PRUint32)i + aOffset + 1) < childCount) {
1226 : // There is a content node that wants a frame.
1227 0 : nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1);
1228 :
1229 : nsIFrame* existingFrame;
1230 0 : if (!IsListItemChild(this, nextContent, &existingFrame)) {
1231 0 : return GetNextItemBox(aBox, ++aOffset, aCreated);
1232 : }
1233 0 : if (!existingFrame) {
1234 : // Either append the new frame, or insert it after the current frame
1235 0 : bool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0;
1236 0 : nsIFrame* prevFrame = isAppend ? nsnull : aBox;
1237 :
1238 0 : nsPresContext* presContext = PresContext();
1239 0 : nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
1240 : fc->CreateListBoxContent(presContext, this, prevFrame, nextContent,
1241 0 : &result, isAppend, false, nsnull);
1242 :
1243 0 : if (result) {
1244 0 : if (aCreated)
1245 0 : *aCreated = true;
1246 : } else
1247 0 : return GetNextItemBox(aBox, ++aOffset, aCreated);
1248 : } else {
1249 0 : result = existingFrame;
1250 : }
1251 :
1252 0 : mLinkupFrame = nsnull;
1253 : }
1254 : }
1255 :
1256 0 : if (!result)
1257 0 : return nsnull;
1258 :
1259 0 : mBottomFrame = result;
1260 :
1261 0 : NS_ASSERTION(!result->IsBoxFrame() || result->GetParent() == this,
1262 : "returning frame that is not in childlist");
1263 :
1264 0 : return result->IsBoxFrame() ? result : nsnull;
1265 : }
1266 :
1267 : bool
1268 0 : nsListBoxBodyFrame::ContinueReflow(nscoord height)
1269 : {
1270 : #ifdef ACCESSIBILITY
1271 0 : if (nsIPresShell::IsAccessibilityActive()) {
1272 : // Create all the frames at once so screen readers and
1273 : // onscreen keyboards can see the full list right away
1274 0 : return true;
1275 : }
1276 : #endif
1277 :
1278 0 : if (height <= 0) {
1279 0 : nsIFrame* lastChild = GetLastFrame();
1280 0 : nsIFrame* startingPoint = mBottomFrame;
1281 0 : if (startingPoint == nsnull) {
1282 : // We just want to delete everything but the first item.
1283 0 : startingPoint = GetFirstFrame();
1284 : }
1285 :
1286 0 : if (lastChild != startingPoint) {
1287 : // We have some hangers on (probably caused by shrinking the size of the window).
1288 : // Nuke them.
1289 0 : nsIFrame* currFrame = startingPoint->GetNextSibling();
1290 0 : nsBoxLayoutState state(PresContext());
1291 :
1292 : nsCSSFrameConstructor* fc =
1293 0 : PresContext()->PresShell()->FrameConstructor();
1294 0 : fc->BeginUpdate();
1295 0 : while (currFrame) {
1296 0 : nsIFrame* nextFrame = currFrame->GetNextSibling();
1297 0 : RemoveChildFrame(state, currFrame);
1298 0 : currFrame = nextFrame;
1299 : }
1300 0 : fc->EndUpdate();
1301 :
1302 0 : PresContext()->PresShell()->
1303 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1304 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1305 : }
1306 0 : return false;
1307 : }
1308 : else
1309 0 : return true;
1310 : }
1311 :
1312 : NS_IMETHODIMP
1313 0 : nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList& aFrameList)
1314 : {
1315 : // append them after
1316 0 : nsBoxLayoutState state(PresContext());
1317 0 : const nsFrameList::Slice& newFrames = mFrames.AppendFrames(nsnull, aFrameList);
1318 0 : if (mLayoutManager)
1319 0 : mLayoutManager->ChildrenAppended(this, state, newFrames);
1320 0 : PresContext()->PresShell()->
1321 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1322 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1323 :
1324 0 : return NS_OK;
1325 : }
1326 :
1327 : NS_IMETHODIMP
1328 0 : nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame,
1329 : nsFrameList& aFrameList)
1330 : {
1331 : // insert the frames to our info list
1332 0 : nsBoxLayoutState state(PresContext());
1333 : const nsFrameList::Slice& newFrames =
1334 0 : mFrames.InsertFrames(nsnull, aPrevFrame, aFrameList);
1335 0 : if (mLayoutManager)
1336 0 : mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames);
1337 0 : PresContext()->PresShell()->
1338 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1339 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1340 :
1341 0 : return NS_OK;
1342 : }
1343 :
1344 : //
1345 : // Called by nsCSSFrameConstructor when a new listitem content is inserted.
1346 : //
1347 : void
1348 0 : nsListBoxBodyFrame::OnContentInserted(nsPresContext* aPresContext, nsIContent* aChildContent)
1349 : {
1350 0 : if (mRowCount >= 0)
1351 0 : ++mRowCount;
1352 :
1353 : // The RDF content builder will build content nodes such that they are all
1354 : // ready when OnContentInserted is first called, meaning the first call
1355 : // to CreateRows will create all the frames, but OnContentInserted will
1356 : // still be called again for each content node - so we need to make sure
1357 : // that the frame for each content node hasn't already been created.
1358 0 : nsIFrame* childFrame = aChildContent->GetPrimaryFrame();
1359 0 : if (childFrame)
1360 0 : return;
1361 :
1362 : PRInt32 siblingIndex;
1363 0 : nsCOMPtr<nsIContent> nextSiblingContent;
1364 0 : GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex);
1365 :
1366 : // if we're inserting our item before the first visible content,
1367 : // then we need to shift all rows down by one
1368 0 : if (siblingIndex >= 0 && siblingIndex-1 <= mCurrentIndex) {
1369 0 : mTopFrame = nsnull;
1370 0 : mRowsToPrepend = 1;
1371 0 : } else if (nextSiblingContent) {
1372 : // we may be inserting before a frame that is on screen
1373 0 : nsIFrame* nextSiblingFrame = nextSiblingContent->GetPrimaryFrame();
1374 0 : mLinkupFrame = nextSiblingFrame;
1375 : }
1376 :
1377 0 : CreateRows();
1378 0 : PresContext()->PresShell()->
1379 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1380 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1381 : }
1382 :
1383 : //
1384 : // Called by nsCSSFrameConstructor when listitem content is removed.
1385 : //
1386 : void
1387 0 : nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext,
1388 : nsIContent* aContainer,
1389 : nsIFrame* aChildFrame,
1390 : nsIContent* aOldNextSibling)
1391 : {
1392 0 : NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this,
1393 : "Removing frame that's not our child... Not good");
1394 :
1395 0 : if (mRowCount >= 0)
1396 0 : --mRowCount;
1397 :
1398 0 : if (aContainer) {
1399 0 : if (!aChildFrame) {
1400 : // The row we are removing is out of view, so we need to try to
1401 : // determine the index of its next sibling.
1402 0 : PRInt32 siblingIndex = -1;
1403 0 : if (aOldNextSibling) {
1404 0 : nsCOMPtr<nsIContent> nextSiblingContent;
1405 : GetListItemNextSibling(aOldNextSibling,
1406 0 : getter_AddRefs(nextSiblingContent),
1407 0 : siblingIndex);
1408 : }
1409 :
1410 : // if the row being removed is off-screen and above the top frame, we need to
1411 : // adjust our top index and tell the scrollbar to shift up one row.
1412 0 : if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) {
1413 0 : NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0");
1414 0 : --mCurrentIndex;
1415 0 : mYPosition = mCurrentIndex*mRowHeight;
1416 0 : VerticalScroll(mYPosition);
1417 : }
1418 0 : } else if (mCurrentIndex > 0) {
1419 : // At this point, we know we have a scrollbar, and we need to know
1420 : // if we are scrolled to the last row. In this case, the behavior
1421 : // of the scrollbar is to stay locked to the bottom. Since we are
1422 : // removing visible content, the first visible row will have to move
1423 : // down by one, and we will have to insert a new frame at the top.
1424 :
1425 : // if the last content node has a frame, we are scrolled to the bottom
1426 0 : ChildIterator iter, last;
1427 0 : ChildIterator::Init(mContent, &iter, &last);
1428 0 : if (iter != last) {
1429 0 : iter = last;
1430 0 : --iter;
1431 0 : nsIContent *lastChild = *iter;
1432 0 : nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame();
1433 :
1434 0 : if (lastChildFrame) {
1435 0 : mTopFrame = nsnull;
1436 0 : mRowsToPrepend = 1;
1437 0 : --mCurrentIndex;
1438 0 : mYPosition = mCurrentIndex*mRowHeight;
1439 0 : VerticalScroll(mYPosition);
1440 : }
1441 : }
1442 : }
1443 : }
1444 :
1445 : // if we're removing the top row, the new top row is the next row
1446 0 : if (mTopFrame && mTopFrame == aChildFrame)
1447 0 : mTopFrame = mTopFrame->GetNextSibling();
1448 :
1449 : // Go ahead and delete the frame.
1450 0 : nsBoxLayoutState state(aPresContext);
1451 0 : if (aChildFrame) {
1452 0 : RemoveChildFrame(state, aChildFrame);
1453 : }
1454 :
1455 0 : PresContext()->PresShell()->
1456 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1457 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1458 0 : }
1459 :
1460 : void
1461 0 : nsListBoxBodyFrame::GetListItemContentAt(PRInt32 aIndex, nsIContent** aContent)
1462 : {
1463 0 : *aContent = nsnull;
1464 :
1465 0 : PRInt32 itemsFound = 0;
1466 0 : ChildIterator iter, last;
1467 0 : for (ChildIterator::Init(mContent, &iter, &last);
1468 : iter != last;
1469 : ++iter) {
1470 0 : nsIContent *kid = (*iter);
1471 0 : if (kid->Tag() == nsGkAtoms::listitem) {
1472 0 : ++itemsFound;
1473 0 : if (itemsFound-1 == aIndex) {
1474 0 : *aContent = kid;
1475 0 : NS_IF_ADDREF(*aContent);
1476 0 : return;
1477 : }
1478 : }
1479 : }
1480 : }
1481 :
1482 : void
1483 0 : nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, PRInt32& aSiblingIndex)
1484 : {
1485 0 : *aContent = nsnull;
1486 0 : aSiblingIndex = -1;
1487 0 : nsIContent *prevKid = nsnull;
1488 0 : ChildIterator iter, last;
1489 0 : for (ChildIterator::Init(mContent, &iter, &last);
1490 : iter != last;
1491 : ++iter) {
1492 0 : nsIContent *kid = (*iter);
1493 :
1494 0 : if (kid->Tag() == nsGkAtoms::listitem) {
1495 0 : ++aSiblingIndex;
1496 0 : if (prevKid == aListItem) {
1497 0 : *aContent = kid;
1498 0 : NS_IF_ADDREF(*aContent);
1499 0 : return;
1500 : }
1501 : }
1502 0 : prevKid = kid;
1503 : }
1504 :
1505 0 : aSiblingIndex = -1; // no match, so there is no next sibling
1506 : }
1507 :
1508 : void
1509 0 : nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState,
1510 : nsIFrame *aFrame)
1511 : {
1512 0 : if (!mFrames.ContainsFrame(aFrame)) {
1513 0 : NS_ERROR("tried to remove a child frame which isn't our child");
1514 0 : return;
1515 : }
1516 :
1517 0 : if (aFrame == GetContentInsertionFrame()) {
1518 : // Don't touch that one
1519 0 : return;
1520 : }
1521 :
1522 : #ifdef ACCESSIBILITY
1523 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
1524 0 : if (accService) {
1525 0 : nsIContent* content = aFrame->GetContent();
1526 : accService->ContentRemoved(PresContext()->PresShell(), content->GetParent(),
1527 0 : content);
1528 : }
1529 : #endif
1530 :
1531 0 : mFrames.RemoveFrame(aFrame);
1532 0 : if (mLayoutManager)
1533 0 : mLayoutManager->ChildrenRemoved(this, aState, aFrame);
1534 0 : aFrame->Destroy();
1535 : }
1536 :
1537 : // Creation Routines ///////////////////////////////////////////////////////////////////////
1538 :
1539 : already_AddRefed<nsBoxLayout> NS_NewListBoxLayout();
1540 :
1541 : nsIFrame*
1542 0 : NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
1543 : {
1544 0 : nsCOMPtr<nsBoxLayout> layout = NS_NewListBoxLayout();
1545 0 : if (!layout) {
1546 0 : return nsnull;
1547 : }
1548 :
1549 0 : return new (aPresShell) nsListBoxBodyFrame(aPresShell, aContext, layout);
1550 : }
1551 :
1552 0 : NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame)
|