layout/xul/nsListBoxBodyFrame.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

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

mercurial