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