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