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