1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 // 7 // Eric Vaughan 8 // Netscape Communications 9 // 10 // See documentation in associated header file 11 // 12 13 #include "nsGrid.h" 14 #include "nsGridRowGroupLayout.h" 15 #include "nsBox.h" 16 #include "nsIScrollableFrame.h" 17 #include "nsSprocketLayout.h" 18 #include "nsGridLayout2.h" 19 #include "nsGridRow.h" 20 #include "nsGridCell.h" 21 #include "mozilla/ReflowInput.h" 22 23 /* 24 The grid control expands the idea of boxes from 1 dimension to 2 dimensions. 25 It works by allowing the XUL to define a collection of rows and columns and then 26 stacking them on top of each other. Here is and example. 27 28 Example 1: 29 30 <grid> 31 <columns> 32 <column/> 33 <column/> 34 </columns> 35 36 <rows> 37 <row/> 38 <row/> 39 </rows> 40 </grid> 41 42 example 2: 43 44 <grid> 45 <columns> 46 <column flex="1"/> 47 <column flex="1"/> 48 </columns> 49 50 <rows> 51 <row> 52 <text value="hello"/> 53 <text value="there"/> 54 </row> 55 </rows> 56 </grid> 57 58 example 3: 59 60 <grid> 61 62 <rows> 63 <row> 64 <text value="hello"/> 65 <text value="there"/> 66 </row> 67 </rows> 68 69 <columns> 70 <column> 71 <text value="Hey I'm in the column and I'm on top!"/> 72 </column> 73 <column/> 74 </columns> 75 76 </grid> 77 78 Usually the columns are first and the rows are second, so the rows will be drawn on top of the columns. 79 You can reverse this by defining the rows first. 80 Other tags are then placed in the <row> or <column> tags causing the grid to accommodate everyone. 81 It does this by creating 3 things: A cellmap, a row list, and a column list. The cellmap is a 2 82 dimensional array of nsGridCells. Each cell contains 2 boxes. One cell from the column list 83 and one from the row list. When a cell is asked for its size it returns that smallest size it can 84 be to accommodate the 2 cells. Row lists and Column lists use the same data structure: nsGridRow. 85 Essentially a row and column are the same except a row goes alone the x axis and a column the y. 86 To make things easier and save code everything is written in terms of the x dimension. A flag is 87 passed in called "isHorizontal" that can flip the calculations to the y axis. 88 89 Usually the number of cells in a row match the number of columns, but not always. 90 It is possible to define 5 columns for a grid but have 10 cells in one of the rows. 91 In this case 5 extra columns will be added to the column list to handle the situation. 92 These are called extraColumns/Rows. 93 */ 94 95 using namespace mozilla; 96 97 nsGrid::nsGrid():mBox(nullptr), 98 mRowsBox(nullptr), 99 mColumnsBox(nullptr), 100 mNeedsRebuild(true), 101 mRowCount(0), 102 mColumnCount(0), 103 mExtraRowCount(0), 104 mExtraColumnCount(0), 105 mMarkingDirty(false) 106 { 107 MOZ_COUNT_CTOR(nsGrid); 108 } 109 110 nsGrid::~nsGrid() 111 { 112 FreeMap(); 113 MOZ_COUNT_DTOR(nsGrid); 114 } 115 116 /* 117 * This is called whenever something major happens in the grid. And example 118 * might be when many cells or row are added. It sets a flag signaling that 119 * all the grids caches information should be recalculated. 120 */ 121 void 122 nsGrid::NeedsRebuild(nsBoxLayoutState& aState) 123 { 124 if (mNeedsRebuild) 125 return; 126 127 // iterate through columns and rows and dirty them 128 mNeedsRebuild = true; 129 130 // find the new row and column box. They could have 131 // been changed. 132 mRowsBox = nullptr; 133 mColumnsBox = nullptr; 134 FindRowsAndColumns(&mRowsBox, &mColumnsBox); 135 136 // tell all the rows and columns they are dirty 137 DirtyRows(mRowsBox, aState); 138 DirtyRows(mColumnsBox, aState); 139 } 140 141 142 143 /** 144 * If we are marked for rebuild. Then build everything 145 */ 146 void 147 nsGrid::RebuildIfNeeded() 148 { 149 if (!mNeedsRebuild) 150 return; 151 152 mNeedsRebuild = false; 153 154 // find the row and columns frames 155 FindRowsAndColumns(&mRowsBox, &mColumnsBox); 156 157 // count the rows and columns 158 int32_t computedRowCount = 0; 159 int32_t computedColumnCount = 0; 160 int32_t rowCount = 0; 161 int32_t columnCount = 0; 162 163 CountRowsColumns(mRowsBox, rowCount, computedColumnCount); 164 CountRowsColumns(mColumnsBox, columnCount, computedRowCount); 165 166 // computedRowCount are the actual number of rows as determined by the 167 // columns children. 168 // computedColumnCount are the number of columns as determined by the number 169 // of rows children. 170 // We can use this information to see how many extra columns or rows we need. 171 // This can happen if there are are more children in a row that number of columns 172 // defined. Example: 173 // 174 // <columns> 175 // <column/> 176 // </columns> 177 // 178 // <rows> 179 // <row> 180 // <button/><button/> 181 // </row> 182 // </rows> 183 // 184 // computedColumnCount = 2 // for the 2 buttons in the row tag 185 // computedRowCount = 0 // there is nothing in the column tag 186 // mColumnCount = 1 // one column defined 187 // mRowCount = 1 // one row defined 188 // 189 // So in this case we need to make 1 extra column. 190 // 191 192 // Make sure to update mExtraColumnCount no matter what, since it might 193 // happen that we now have as many columns as are defined, and we wouldn't 194 // want to have a positive mExtraColumnCount hanging about in that case! 195 mExtraColumnCount = computedColumnCount - columnCount; 196 if (computedColumnCount > columnCount) { 197 columnCount = computedColumnCount; 198 } 199 200 // Same for rows. 201 mExtraRowCount = computedRowCount - rowCount; 202 if (computedRowCount > rowCount) { 203 rowCount = computedRowCount; 204 } 205 206 // build and poplulate row and columns arrays 207 mRows = BuildRows(mRowsBox, rowCount, true); 208 mColumns = BuildRows(mColumnsBox, columnCount, false); 209 210 // build and populate the cell map 211 mCellMap = BuildCellMap(rowCount, columnCount); 212 213 mRowCount = rowCount; 214 mColumnCount = columnCount; 215 216 // populate the cell map from column and row children 217 PopulateCellMap(mRows.get(), mColumns.get(), mRowCount, mColumnCount, true); 218 PopulateCellMap(mColumns.get(), mRows.get(), mColumnCount, mRowCount, false); 219 } 220 221 void 222 nsGrid::FreeMap() 223 { 224 mRows = nullptr; 225 mColumns = nullptr; 226 mCellMap = nullptr; 227 mColumnCount = 0; 228 mRowCount = 0; 229 mExtraColumnCount = 0; 230 mExtraRowCount = 0; 231 mRowsBox = nullptr; 232 mColumnsBox = nullptr; 233 } 234 235 /** 236 * finds the first <rows> and <columns> tags in the <grid> tag 237 */ 238 void 239 nsGrid::FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns) 240 { 241 *aRows = nullptr; 242 *aColumns = nullptr; 243 244 // find the boxes that contain our rows and columns 245 nsIFrame* child = nullptr; 246 // if we have <grid></grid> then mBox will be null (bug 125689) 247 if (mBox) 248 child = nsBox::GetChildXULBox(mBox); 249 250 while(child) 251 { 252 nsIFrame* oldBox = child; 253 nsIScrollableFrame *scrollFrame = do_QueryFrame(child); 254 if (scrollFrame) { 255 nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); 256 NS_ASSERTION(scrolledFrame,"Error no scroll frame!!"); 257 child = do_QueryFrame(scrolledFrame); 258 } 259 260 nsCOMPtr<nsIGridPart> monument = GetPartFromBox(child); 261 if (monument) 262 { 263 nsGridRowGroupLayout* rowGroup = monument->CastToRowGroupLayout(); 264 if (rowGroup) { 265 bool isHorizontal = !nsSprocketLayout::IsXULHorizontal(child); 266 if (isHorizontal) 267 *aRows = child; 268 else 269 *aColumns = child; 270 271 if (*aRows && *aColumns) 272 return; 273 } 274 } 275 276 if (scrollFrame) { 277 child = oldBox; 278 } 279 280 child = nsBox::GetNextXULBox(child); 281 } 282 } 283 284 /** 285 * Count the number of rows and columns in the given box. aRowCount well become the actual number 286 * rows defined in the xul. aComputedColumnCount will become the number of columns by counting the number 287 * of cells in each row. 288 */ 289 void 290 nsGrid::CountRowsColumns(nsIFrame* aRowBox, int32_t& aRowCount, int32_t& aComputedColumnCount) 291 { 292 aRowCount = 0; 293 aComputedColumnCount = 0; 294 // get the rowboxes layout manager. Then ask it to do the work for us 295 if (aRowBox) { 296 nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aRowBox); 297 if (monument) 298 monument->CountRowsColumns(aRowBox, aRowCount, aComputedColumnCount); 299 } 300 } 301 302 303 /** 304 * Given the number of rows create nsGridRow objects for them and full them out. 305 */ 306 UniquePtr<nsGridRow[]> 307 nsGrid::BuildRows(nsIFrame* aBox, int32_t aRowCount, bool aIsHorizontal) 308 { 309 // if no rows then return null 310 if (aRowCount == 0) { 311 return nullptr; 312 } 313 314 // create the array 315 UniquePtr<nsGridRow[]> row; 316 317 // only create new rows if we have to. Reuse old rows. 318 if (aIsHorizontal) 319 { 320 if (aRowCount > mRowCount) { 321 row = MakeUnique<nsGridRow[]>(aRowCount); 322 } else { 323 for (int32_t i=0; i < mRowCount; i++) 324 mRows[i].Init(nullptr, false); 325 326 row = Move(mRows); 327 } 328 } else { 329 if (aRowCount > mColumnCount) { 330 row = MakeUnique<nsGridRow[]>(aRowCount); 331 } else { 332 for (int32_t i=0; i < mColumnCount; i++) 333 mColumns[i].Init(nullptr, false); 334 335 row = Move(mColumns); 336 } 337 } 338 339 // populate it if we can. If not it will contain only dynamic columns 340 if (aBox) 341 { 342 nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aBox); 343 if (monument) { 344 monument->BuildRows(aBox, row.get()); 345 } 346 } 347 348 return row; 349 } 350 351 352 /** 353 * Given the number of rows and columns. Build a cellmap 354 */ 355 UniquePtr<nsGridCell[]> 356 nsGrid::BuildCellMap(int32_t aRows, int32_t aColumns) 357 { 358 int32_t size = aRows*aColumns; 359 int32_t oldsize = mRowCount*mColumnCount; 360 if (size == 0) { 361 return nullptr; 362 } 363 364 if (size > oldsize) { 365 return MakeUnique<nsGridCell[]>(size); 366 } 367 368 // clear out cellmap 369 for (int32_t i=0; i < oldsize; i++) { 370 mCellMap[i].SetBoxInRow(nullptr); 371 mCellMap[i].SetBoxInColumn(nullptr); 372 } 373 return Move(mCellMap); 374 } 375 376 /** 377 * Run through all the cells in the rows and columns and populate then with 2 cells. One from the row and one 378 * from the column 379 */ 380 void 381 nsGrid::PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns, int32_t aRowCount, int32_t aColumnCount, bool aIsHorizontal) 382 { 383 if (!aRows) 384 return; 385 386 // look through the columns 387 int32_t j = 0; 388 389 for(int32_t i=0; i < aRowCount; i++) 390 { 391 nsIFrame* child = nullptr; 392 nsGridRow* row = &aRows[i]; 393 394 // skip bogus rows. They have no cells 395 if (row->mIsBogus) 396 continue; 397 398 child = row->mBox; 399 if (child) { 400 child = nsBox::GetChildXULBox(child); 401 402 j = 0; 403 404 while(child && j < aColumnCount) 405 { 406 // skip bogus column. They have no cells 407 nsGridRow* column = &aColumns[j]; 408 if (column->mIsBogus) 409 { 410 j++; 411 continue; 412 } 413 414 if (aIsHorizontal) 415 GetCellAt(j,i)->SetBoxInRow(child); 416 else 417 GetCellAt(i,j)->SetBoxInColumn(child); 418 419 child = nsBox::GetNextXULBox(child); 420 421 j++; 422 } 423 } 424 } 425 } 426 427 /** 428 * Run through the rows in the given box and mark them dirty so they 429 * will get recalculated and get a layout. 430 */ 431 void 432 nsGrid::DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState) 433 { 434 // make sure we prevent others from dirtying things. 435 mMarkingDirty = true; 436 437 // if the box is a grid part have it recursively hand it. 438 if (aRowBox) { 439 nsCOMPtr<nsIGridPart> part = GetPartFromBox(aRowBox); 440 if (part) 441 part->DirtyRows(aRowBox, aState); 442 } 443 444 mMarkingDirty = false; 445 } 446 447 nsGridRow* 448 nsGrid::GetColumnAt(int32_t aIndex, bool aIsHorizontal) 449 { 450 return GetRowAt(aIndex, !aIsHorizontal); 451 } 452 453 nsGridRow* 454 nsGrid::GetRowAt(int32_t aIndex, bool aIsHorizontal) 455 { 456 RebuildIfNeeded(); 457 458 if (aIsHorizontal) { 459 NS_ASSERTION(aIndex < mRowCount && aIndex >= 0, "Index out of range"); 460 return &mRows[aIndex]; 461 } else { 462 NS_ASSERTION(aIndex < mColumnCount && aIndex >= 0, "Index out of range"); 463 return &mColumns[aIndex]; 464 } 465 } 466 467 nsGridCell* 468 nsGrid::GetCellAt(int32_t aX, int32_t aY) 469 { 470 RebuildIfNeeded(); 471 472 NS_ASSERTION(aY < mRowCount && aY >= 0, "Index out of range"); 473 NS_ASSERTION(aX < mColumnCount && aX >= 0, "Index out of range"); 474 return &mCellMap[aY*mColumnCount+aX]; 475 } 476 477 int32_t 478 nsGrid::GetExtraColumnCount(bool aIsHorizontal) 479 { 480 return GetExtraRowCount(!aIsHorizontal); 481 } 482 483 int32_t 484 nsGrid::GetExtraRowCount(bool aIsHorizontal) 485 { 486 RebuildIfNeeded(); 487 488 if (aIsHorizontal) 489 return mExtraRowCount; 490 else 491 return mExtraColumnCount; 492 } 493 494 495 /** 496 * These methods return the preferred, min, max sizes for a given row index. 497 * aIsHorizontal if aIsHorizontal is true. If you pass false you will get the inverse. 498 * As if you called GetPrefColumnSize(aState, index, aPref) 499 */ 500 nsSize 501 nsGrid::GetPrefRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal) 502 { 503 nsSize size(0,0); 504 if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal))) 505 return size; 506 507 nscoord height = GetPrefRowHeight(aState, aRowIndex, aIsHorizontal); 508 SetLargestSize(size, height, aIsHorizontal); 509 510 return size; 511 } 512 513 nsSize 514 nsGrid::GetMinRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal) 515 { 516 nsSize size(0,0); 517 if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal))) 518 return size; 519 520 nscoord height = GetMinRowHeight(aState, aRowIndex, aIsHorizontal); 521 SetLargestSize(size, height, aIsHorizontal); 522 523 return size; 524 } 525 526 nsSize 527 nsGrid::GetMaxRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal) 528 { 529 nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE); 530 if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal))) 531 return size; 532 533 nscoord height = GetMaxRowHeight(aState, aRowIndex, aIsHorizontal); 534 SetSmallestSize(size, height, aIsHorizontal); 535 536 return size; 537 } 538 539 // static 540 nsIGridPart* 541 nsGrid::GetPartFromBox(nsIFrame* aBox) 542 { 543 if (!aBox) 544 return nullptr; 545 546 nsBoxLayout* layout = aBox->GetXULLayoutManager(); 547 return layout ? layout->AsGridPart() : nullptr; 548 } 549 550 nsMargin 551 nsGrid::GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal) 552 { 553 nsMargin margin(0,0,0,0); 554 // walk the boxes parent chain getting the border/padding/margin of our parent rows 555 556 // first get the layour manager 557 nsIGridPart* part = GetPartFromBox(aBox); 558 if (part) 559 margin = part->GetTotalMargin(aBox, aIsHorizontal); 560 561 return margin; 562 } 563 564 /** 565 * The first and last rows can be affected by <rows> tags with borders or margin 566 * gets first and last rows and their indexes. 567 * If it fails because there are no rows then: 568 * FirstRow is nullptr 569 * LastRow is nullptr 570 * aFirstIndex = -1 571 * aLastIndex = -1 572 */ 573 void 574 nsGrid::GetFirstAndLastRow(int32_t& aFirstIndex, 575 int32_t& aLastIndex, 576 nsGridRow*& aFirstRow, 577 nsGridRow*& aLastRow, 578 bool aIsHorizontal) 579 { 580 aFirstRow = nullptr; 581 aLastRow = nullptr; 582 aFirstIndex = -1; 583 aLastIndex = -1; 584 585 int32_t count = GetRowCount(aIsHorizontal); 586 587 if (count == 0) 588 return; 589 590 591 // We could have collapsed columns either before or after our index. 592 // they should not count. So if we are the 5th row and the first 4 are 593 // collaped we become the first row. Or if we are the 9th row and 594 // 10 up to the last row are collapsed we then become the last. 595 596 // see if we are first 597 int32_t i; 598 for (i=0; i < count; i++) 599 { 600 nsGridRow* row = GetRowAt(i,aIsHorizontal); 601 if (!row->IsXULCollapsed()) { 602 aFirstIndex = i; 603 aFirstRow = row; 604 break; 605 } 606 } 607 608 // see if we are last 609 for (i=count-1; i >= 0; i--) 610 { 611 nsGridRow* row = GetRowAt(i,aIsHorizontal); 612 if (!row->IsXULCollapsed()) { 613 aLastIndex = i; 614 aLastRow = row; 615 break; 616 } 617 618 } 619 } 620 621 /** 622 * A row can have a top and bottom offset. Usually this is just the top and bottom border/padding. 623 * However if the row is the first or last it could be affected by the fact a column or columns could 624 * have a top or bottom margin. 625 */ 626 void 627 nsGrid::GetRowOffsets(int32_t aIndex, nscoord& aTop, nscoord& aBottom, bool aIsHorizontal) 628 { 629 630 RebuildIfNeeded(); 631 632 nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); 633 634 if (row->IsOffsetSet()) 635 { 636 aTop = row->mTop; 637 aBottom = row->mBottom; 638 return; 639 } 640 641 // first get the rows top and bottom border and padding 642 nsIFrame* box = row->GetBox(); 643 644 // add up all the padding 645 nsMargin margin(0,0,0,0); 646 nsMargin border(0,0,0,0); 647 nsMargin padding(0,0,0,0); 648 nsMargin totalBorderPadding(0,0,0,0); 649 nsMargin totalMargin(0,0,0,0); 650 651 // if there is a box and it's not bogus take its 652 // borders padding into account 653 if (box && !row->mIsBogus) 654 { 655 if (!box->IsXULCollapsed()) 656 { 657 // get real border and padding. GetXULBorderAndPadding 658 // is redefined on nsGridRowLeafFrame. If we called it here 659 // we would be in finite recurson. 660 box->GetXULBorder(border); 661 box->GetXULPadding(padding); 662 663 totalBorderPadding += border; 664 totalBorderPadding += padding; 665 } 666 667 // if we are the first or last row 668 // take into account <rows> tags around us 669 // that could have borders or margins. 670 // fortunately they only affect the first 671 // and last row inside the <rows> tag 672 673 totalMargin = GetBoxTotalMargin(box, aIsHorizontal); 674 } 675 676 if (aIsHorizontal) { 677 row->mTop = totalBorderPadding.top; 678 row->mBottom = totalBorderPadding.bottom; 679 row->mTopMargin = totalMargin.top; 680 row->mBottomMargin = totalMargin.bottom; 681 } else { 682 row->mTop = totalBorderPadding.left; 683 row->mBottom = totalBorderPadding.right; 684 row->mTopMargin = totalMargin.left; 685 row->mBottomMargin = totalMargin.right; 686 } 687 688 // if we are the first or last row take into account the top and bottom borders 689 // of each columns. 690 691 // If we are the first row then get the largest top border/padding in 692 // our columns. If that's larger than the rows top border/padding use it. 693 694 // If we are the last row then get the largest bottom border/padding in 695 // our columns. If that's larger than the rows bottom border/padding use it. 696 int32_t firstIndex = 0; 697 int32_t lastIndex = 0; 698 nsGridRow* firstRow = nullptr; 699 nsGridRow* lastRow = nullptr; 700 GetFirstAndLastRow(firstIndex, lastIndex, firstRow, lastRow, aIsHorizontal); 701 702 if (aIndex == firstIndex || aIndex == lastIndex) { 703 nscoord maxTop = 0; 704 nscoord maxBottom = 0; 705 706 // run through the columns. Look at each column 707 // pick the largest top border or bottom border 708 int32_t count = GetColumnCount(aIsHorizontal); 709 710 for (int32_t i=0; i < count; i++) 711 { 712 nsMargin totalChildBorderPadding(0,0,0,0); 713 714 nsGridRow* column = GetColumnAt(i,aIsHorizontal); 715 nsIFrame* box = column->GetBox(); 716 717 if (box) 718 { 719 // ignore collapsed children 720 if (!box->IsXULCollapsed()) 721 { 722 // include the margin of the columns. To the row 723 // at this point border/padding and margins all added 724 // up to more needed space. 725 margin = GetBoxTotalMargin(box, !aIsHorizontal); 726 // get real border and padding. GetXULBorderAndPadding 727 // is redefined on nsGridRowLeafFrame. If we called it here 728 // we would be in finite recurson. 729 box->GetXULBorder(border); 730 box->GetXULPadding(padding); 731 totalChildBorderPadding += border; 732 totalChildBorderPadding += padding; 733 totalChildBorderPadding += margin; 734 } 735 736 nscoord top; 737 nscoord bottom; 738 739 // pick the largest top margin 740 if (aIndex == firstIndex) { 741 if (aIsHorizontal) { 742 top = totalChildBorderPadding.top; 743 } else { 744 top = totalChildBorderPadding.left; 745 } 746 if (top > maxTop) 747 maxTop = top; 748 } 749 750 // pick the largest bottom margin 751 if (aIndex == lastIndex) { 752 if (aIsHorizontal) { 753 bottom = totalChildBorderPadding.bottom; 754 } else { 755 bottom = totalChildBorderPadding.right; 756 } 757 if (bottom > maxBottom) 758 maxBottom = bottom; 759 } 760 761 } 762 763 // If the biggest top border/padding the columns is larger than this rows top border/padding 764 // the use it. 765 if (aIndex == firstIndex) { 766 if (maxTop > (row->mTop + row->mTopMargin)) 767 row->mTop = maxTop - row->mTopMargin; 768 } 769 770 // If the biggest bottom border/padding the columns is larger than this rows bottom border/padding 771 // the use it. 772 if (aIndex == lastIndex) { 773 if (maxBottom > (row->mBottom + row->mBottomMargin)) 774 row->mBottom = maxBottom - row->mBottomMargin; 775 } 776 } 777 } 778 779 aTop = row->mTop; 780 aBottom = row->mBottom; 781 } 782 783 /** 784 * These methods return the preferred, min, max coord for a given row index if 785 * aIsHorizontal is true. If you pass false you will get the inverse. 786 * As if you called GetPrefColumnHeight(aState, index, aPref). 787 */ 788 nscoord 789 nsGrid::GetPrefRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) 790 { 791 RebuildIfNeeded(); 792 793 nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); 794 795 if (row->IsXULCollapsed()) 796 return 0; 797 798 if (row->IsPrefSet()) 799 return row->mPref; 800 801 nsIFrame* box = row->mBox; 802 803 // set in CSS? 804 if (box) 805 { 806 bool widthSet, heightSet; 807 nsSize cssSize(-1, -1); 808 nsIFrame::AddXULPrefSize(box, cssSize, widthSet, heightSet); 809 810 row->mPref = GET_HEIGHT(cssSize, aIsHorizontal); 811 812 // yep do nothing. 813 if (row->mPref != -1) 814 return row->mPref; 815 } 816 817 // get the offsets so they are cached. 818 nscoord top; 819 nscoord bottom; 820 GetRowOffsets(aIndex, top, bottom, aIsHorizontal); 821 822 // is the row bogus? If so then just ask it for its size 823 // it should not be affected by cells in the grid. 824 if (row->mIsBogus) 825 { 826 nsSize size(0,0); 827 if (box) 828 { 829 size = box->GetXULPrefSize(aState); 830 nsBox::AddMargin(box, size); 831 nsGridLayout2::AddOffset(box, size); 832 } 833 834 row->mPref = GET_HEIGHT(size, aIsHorizontal); 835 return row->mPref; 836 } 837 838 nsSize size(0,0); 839 840 nsGridCell* child; 841 842 int32_t count = GetColumnCount(aIsHorizontal); 843 844 for (int32_t i=0; i < count; i++) 845 { 846 if (aIsHorizontal) 847 child = GetCellAt(i,aIndex); 848 else 849 child = GetCellAt(aIndex,i); 850 851 // ignore collapsed children 852 if (!child->IsXULCollapsed()) 853 { 854 nsSize childSize = child->GetXULPrefSize(aState); 855 856 nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal); 857 } 858 } 859 860 row->mPref = GET_HEIGHT(size, aIsHorizontal) + top + bottom; 861 862 return row->mPref; 863 } 864 865 nscoord 866 nsGrid::GetMinRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) 867 { 868 RebuildIfNeeded(); 869 870 nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); 871 872 if (row->IsXULCollapsed()) 873 return 0; 874 875 if (row->IsMinSet()) 876 return row->mMin; 877 878 nsIFrame* box = row->mBox; 879 880 // set in CSS? 881 if (box) { 882 bool widthSet, heightSet; 883 nsSize cssSize(-1, -1); 884 nsIFrame::AddXULMinSize(aState, box, cssSize, widthSet, heightSet); 885 886 row->mMin = GET_HEIGHT(cssSize, aIsHorizontal); 887 888 // yep do nothing. 889 if (row->mMin != -1) 890 return row->mMin; 891 } 892 893 // get the offsets so they are cached. 894 nscoord top; 895 nscoord bottom; 896 GetRowOffsets(aIndex, top, bottom, aIsHorizontal); 897 898 // is the row bogus? If so then just ask it for its size 899 // it should not be affected by cells in the grid. 900 if (row->mIsBogus) 901 { 902 nsSize size(0,0); 903 if (box) { 904 size = box->GetXULPrefSize(aState); 905 nsBox::AddMargin(box, size); 906 nsGridLayout2::AddOffset(box, size); 907 } 908 909 row->mMin = GET_HEIGHT(size, aIsHorizontal) + top + bottom; 910 return row->mMin; 911 } 912 913 nsSize size(0,0); 914 915 nsGridCell* child; 916 917 int32_t count = GetColumnCount(aIsHorizontal); 918 919 for (int32_t i=0; i < count; i++) 920 { 921 if (aIsHorizontal) 922 child = GetCellAt(i,aIndex); 923 else 924 child = GetCellAt(aIndex,i); 925 926 // ignore collapsed children 927 if (!child->IsXULCollapsed()) 928 { 929 nsSize childSize = child->GetXULMinSize(aState); 930 931 nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal); 932 } 933 } 934 935 row->mMin = GET_HEIGHT(size, aIsHorizontal); 936 937 return row->mMin; 938 } 939 940 nscoord 941 nsGrid::GetMaxRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) 942 { 943 RebuildIfNeeded(); 944 945 nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); 946 947 if (row->IsXULCollapsed()) 948 return 0; 949 950 if (row->IsMaxSet()) 951 return row->mMax; 952 953 nsIFrame* box = row->mBox; 954 955 // set in CSS? 956 if (box) { 957 bool widthSet, heightSet; 958 nsSize cssSize(-1, -1); 959 nsIFrame::AddXULMaxSize(box, cssSize, widthSet, heightSet); 960 961 row->mMax = GET_HEIGHT(cssSize, aIsHorizontal); 962 963 // yep do nothing. 964 if (row->mMax != -1) 965 return row->mMax; 966 } 967 968 // get the offsets so they are cached. 969 nscoord top; 970 nscoord bottom; 971 GetRowOffsets(aIndex, top, bottom, aIsHorizontal); 972 973 // is the row bogus? If so then just ask it for its size 974 // it should not be affected by cells in the grid. 975 if (row->mIsBogus) 976 { 977 nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE); 978 if (box) { 979 size = box->GetXULPrefSize(aState); 980 nsBox::AddMargin(box, size); 981 nsGridLayout2::AddOffset(box, size); 982 } 983 984 row->mMax = GET_HEIGHT(size, aIsHorizontal); 985 return row->mMax; 986 } 987 988 nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE); 989 990 nsGridCell* child; 991 992 int32_t count = GetColumnCount(aIsHorizontal); 993 994 for (int32_t i=0; i < count; i++) 995 { 996 if (aIsHorizontal) 997 child = GetCellAt(i,aIndex); 998 else 999 child = GetCellAt(aIndex,i); 1000 1001 // ignore collapsed children 1002 if (!child->IsXULCollapsed()) 1003 { 1004 nsSize min = child->GetXULMinSize(aState); 1005 nsSize childSize = nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(aState)); 1006 nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal); 1007 } 1008 } 1009 1010 row->mMax = GET_HEIGHT(size, aIsHorizontal) + top + bottom; 1011 1012 return row->mMax; 1013 } 1014 1015 bool 1016 nsGrid::IsGrid(nsIFrame* aBox) 1017 { 1018 nsIGridPart* part = GetPartFromBox(aBox); 1019 if (!part) 1020 return false; 1021 1022 nsGridLayout2* grid = part->CastToGridLayout(); 1023 1024 if (grid) 1025 return true; 1026 1027 return false; 1028 } 1029 1030 /** 1031 * This get the flexibilty of the row at aIndex. It's not trivial. There are a few 1032 * things we need to look at. Specifically we need to see if any <rows> or <columns> 1033 * tags are around us. Their flexibilty will affect ours. 1034 */ 1035 nscoord 1036 nsGrid::GetRowFlex(int32_t aIndex, bool aIsHorizontal) 1037 { 1038 RebuildIfNeeded(); 1039 1040 nsGridRow* row = GetRowAt(aIndex, aIsHorizontal); 1041 1042 if (row->IsFlexSet()) 1043 return row->mFlex; 1044 1045 nsIFrame* box = row->mBox; 1046 row->mFlex = 0; 1047 1048 if (box) { 1049 1050 // We need our flex but a inflexible row could be around us. If so 1051 // neither are we. However if its the row tag just inside the grid it won't 1052 // affect us. We need to do this for this case: 1053 // <grid> 1054 // <rows> 1055 // <rows> // this is not flexible. So our children should not be flexible 1056 // <row flex="1"/> 1057 // <row flex="1"/> 1058 // </rows> 1059 // <row/> 1060 // </rows> 1061 // </grid> 1062 // 1063 // or.. 1064 // 1065 // <grid> 1066 // <rows> 1067 // <rows> // this is not flexible. So our children should not be flexible 1068 // <rows flex="1"> 1069 // <row flex="1"/> 1070 // <row flex="1"/> 1071 // </rows> 1072 // <row/> 1073 // </rows> 1074 // </row> 1075 // </grid> 1076 1077 1078 // So here is how it looks 1079 // 1080 // <grid> 1081 // <rows> // parentsParent 1082 // <rows> // parent 1083 // <row flex="1"/> 1084 // <row flex="1"/> 1085 // </rows> 1086 // <row/> 1087 // </rows> 1088 // </grid> 1089 1090 // so the answer is simple: 1) Walk our parent chain. 2) If we find 1091 // someone who is not flexible and they aren't the rows immediately in 1092 // the grid. 3) Then we are not flexible 1093 1094 box = GetScrollBox(box); 1095 nsIFrame* parent = nsBox::GetParentXULBox(box); 1096 nsIFrame* parentsParent=nullptr; 1097 1098 while(parent) 1099 { 1100 parent = GetScrollBox(parent); 1101 parentsParent = nsBox::GetParentXULBox(parent); 1102 1103 // if our parents parent is not a grid 1104 // the get its flex. If its 0 then we are 1105 // not flexible. 1106 if (parentsParent) { 1107 if (!IsGrid(parentsParent)) { 1108 nscoord flex = parent->GetXULFlex(); 1109 nsIFrame::AddXULFlex(parent, flex); 1110 if (flex == 0) { 1111 row->mFlex = 0; 1112 return row->mFlex; 1113 } 1114 } else 1115 break; 1116 } 1117 1118 parent = parentsParent; 1119 } 1120 1121 // get the row flex. 1122 row->mFlex = box->GetXULFlex(); 1123 nsIFrame::AddXULFlex(box, row->mFlex); 1124 } 1125 1126 return row->mFlex; 1127 } 1128 1129 void 1130 nsGrid::SetLargestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal) 1131 { 1132 if (aIsHorizontal) { 1133 if (aSize.height < aHeight) 1134 aSize.height = aHeight; 1135 } else { 1136 if (aSize.width < aHeight) 1137 aSize.width = aHeight; 1138 } 1139 } 1140 1141 void 1142 nsGrid::SetSmallestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal) 1143 { 1144 if (aIsHorizontal) { 1145 if (aSize.height > aHeight) 1146 aSize.height = aHeight; 1147 } else { 1148 if (aSize.width < aHeight) 1149 aSize.width = aHeight; 1150 } 1151 } 1152 1153 int32_t 1154 nsGrid::GetRowCount(int32_t aIsHorizontal) 1155 { 1156 RebuildIfNeeded(); 1157 1158 if (aIsHorizontal) 1159 return mRowCount; 1160 else 1161 return mColumnCount; 1162 } 1163 1164 int32_t 1165 nsGrid::GetColumnCount(int32_t aIsHorizontal) 1166 { 1167 return GetRowCount(!aIsHorizontal); 1168 } 1169 1170 /* 1171 * A cell in the given row or columns at the given index has had a child added or removed 1172 */ 1173 void 1174 nsGrid::CellAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) 1175 { 1176 // TBD see if the cell will fit in our current row. If it will 1177 // just add it in. 1178 // but for now rebuild everything. 1179 if (mMarkingDirty) 1180 return; 1181 1182 NeedsRebuild(aState); 1183 } 1184 1185 /** 1186 * A row or columns at the given index had been added or removed 1187 */ 1188 void 1189 nsGrid::RowAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal) 1190 { 1191 // TBD see if we have extra room in the table and just add the new row in 1192 // for now rebuild the world 1193 if (mMarkingDirty) 1194 return; 1195 1196 NeedsRebuild(aState); 1197 } 1198 1199 /* 1200 * Scrollframes are tranparent. If this is given a scrollframe is will return the 1201 * frame inside. If there is no scrollframe it does nothing. 1202 */ 1203 nsIFrame* 1204 nsGrid::GetScrolledBox(nsIFrame* aChild) 1205 { 1206 // first see if it is a scrollframe. If so walk down into it and get the scrolled child 1207 nsIScrollableFrame *scrollFrame = do_QueryFrame(aChild); 1208 if (scrollFrame) { 1209 nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); 1210 NS_ASSERTION(scrolledFrame,"Error no scroll frame!!"); 1211 return scrolledFrame; 1212 } 1213 1214 return aChild; 1215 } 1216 1217 /* 1218 * Scrollframes are tranparent. If this is given a child in a scrollframe is will return the 1219 * scrollframe ourside it. If there is no scrollframe it does nothing. 1220 */ 1221 nsIFrame* 1222 nsGrid::GetScrollBox(nsIFrame* aChild) 1223 { 1224 if (!aChild) 1225 return nullptr; 1226 1227 // get parent 1228 nsIFrame* parent = nsBox::GetParentXULBox(aChild); 1229 1230 // walk up until we find a scrollframe or a part 1231 // if it's a scrollframe return it. 1232 // if it's a parent then the child passed does not 1233 // have a scroll frame immediately wrapped around it. 1234 while (parent) { 1235 nsIScrollableFrame *scrollFrame = do_QueryFrame(parent); 1236 // scrollframe? Yep return it. 1237 if (scrollFrame) 1238 return parent; 1239 1240 nsCOMPtr<nsIGridPart> parentGridRow = GetPartFromBox(parent); 1241 // if a part then just return the child 1242 if (parentGridRow) 1243 break; 1244 1245 parent = nsBox::GetParentXULBox(parent); 1246 } 1247 1248 return aChild; 1249 } 1250 1251 1252 1253 #ifdef DEBUG_grid 1254 void 1255 nsGrid::PrintCellMap() 1256 { 1257 1258 printf("-----Columns------\n"); 1259 for (int x=0; x < mColumnCount; x++) 1260 { 1261 1262 nsGridRow* column = GetColumnAt(x); 1263 printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax); 1264 } 1265 1266 printf("\n-----Rows------\n"); 1267 for (x=0; x < mRowCount; x++) 1268 { 1269 nsGridRow* column = GetRowAt(x); 1270 printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax); 1271 } 1272 1273 printf("\n"); 1274 1275 } 1276 #endif 1277