michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0:
michael@0: //
michael@0: // Eric Vaughan
michael@0: // Netscape Communications
michael@0: //
michael@0: // See documentation in associated header file
michael@0: //
michael@0:
michael@0: #include "nsGrid.h"
michael@0: #include "nsGridRowGroupLayout.h"
michael@0: #include "nsBox.h"
michael@0: #include "nsIScrollableFrame.h"
michael@0: #include "nsSprocketLayout.h"
michael@0: #include "nsGridLayout2.h"
michael@0: #include "nsGridRow.h"
michael@0: #include "nsGridCell.h"
michael@0: #include "nsHTMLReflowState.h"
michael@0:
michael@0: /*
michael@0: The grid control expands the idea of boxes from 1 dimension to 2 dimensions.
michael@0: It works by allowing the XUL to define a collection of rows and columns and then
michael@0: stacking them on top of each other. Here is and example.
michael@0:
michael@0: Example 1:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0: example 2:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0: example 3:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0:
michael@0: Usually the columns are first and the rows are second, so the rows will be drawn on top of the columns.
michael@0: You can reverse this by defining the rows first.
michael@0: Other tags are then placed in the or tags causing the grid to accommodate everyone.
michael@0: It does this by creating 3 things: A cellmap, a row list, and a column list. The cellmap is a 2
michael@0: dimensional array of nsGridCells. Each cell contains 2 boxes. One cell from the column list
michael@0: and one from the row list. When a cell is asked for its size it returns that smallest size it can
michael@0: be to accommodate the 2 cells. Row lists and Column lists use the same data structure: nsGridRow.
michael@0: Essentially a row and column are the same except a row goes alone the x axis and a column the y.
michael@0: To make things easier and save code everything is written in terms of the x dimension. A flag is
michael@0: passed in called "isHorizontal" that can flip the calculations to the y axis.
michael@0:
michael@0: Usually the number of cells in a row match the number of columns, but not always.
michael@0: It is possible to define 5 columns for a grid but have 10 cells in one of the rows.
michael@0: In this case 5 extra columns will be added to the column list to handle the situation.
michael@0: These are called extraColumns/Rows.
michael@0: */
michael@0:
michael@0: nsGrid::nsGrid():mBox(nullptr),
michael@0: mRows(nullptr),
michael@0: mColumns(nullptr),
michael@0: mRowsBox(nullptr),
michael@0: mColumnsBox(nullptr),
michael@0: mNeedsRebuild(true),
michael@0: mRowCount(0),
michael@0: mColumnCount(0),
michael@0: mExtraRowCount(0),
michael@0: mExtraColumnCount(0),
michael@0: mCellMap(nullptr),
michael@0: mMarkingDirty(false)
michael@0: {
michael@0: MOZ_COUNT_CTOR(nsGrid);
michael@0: }
michael@0:
michael@0: nsGrid::~nsGrid()
michael@0: {
michael@0: FreeMap();
michael@0: MOZ_COUNT_DTOR(nsGrid);
michael@0: }
michael@0:
michael@0: /*
michael@0: * This is called whenever something major happens in the grid. And example
michael@0: * might be when many cells or row are added. It sets a flag signaling that
michael@0: * all the grids caches information should be recalculated.
michael@0: */
michael@0: void
michael@0: nsGrid::NeedsRebuild(nsBoxLayoutState& aState)
michael@0: {
michael@0: if (mNeedsRebuild)
michael@0: return;
michael@0:
michael@0: // iterate through columns and rows and dirty them
michael@0: mNeedsRebuild = true;
michael@0:
michael@0: // find the new row and column box. They could have
michael@0: // been changed.
michael@0: mRowsBox = nullptr;
michael@0: mColumnsBox = nullptr;
michael@0: FindRowsAndColumns(&mRowsBox, &mColumnsBox);
michael@0:
michael@0: // tell all the rows and columns they are dirty
michael@0: DirtyRows(mRowsBox, aState);
michael@0: DirtyRows(mColumnsBox, aState);
michael@0: }
michael@0:
michael@0:
michael@0:
michael@0: /**
michael@0: * If we are marked for rebuild. Then build everything
michael@0: */
michael@0: void
michael@0: nsGrid::RebuildIfNeeded()
michael@0: {
michael@0: if (!mNeedsRebuild)
michael@0: return;
michael@0:
michael@0: mNeedsRebuild = false;
michael@0:
michael@0: // find the row and columns frames
michael@0: FindRowsAndColumns(&mRowsBox, &mColumnsBox);
michael@0:
michael@0: // count the rows and columns
michael@0: int32_t computedRowCount = 0;
michael@0: int32_t computedColumnCount = 0;
michael@0: int32_t rowCount = 0;
michael@0: int32_t columnCount = 0;
michael@0:
michael@0: CountRowsColumns(mRowsBox, rowCount, computedColumnCount);
michael@0: CountRowsColumns(mColumnsBox, columnCount, computedRowCount);
michael@0:
michael@0: // computedRowCount are the actual number of rows as determined by the
michael@0: // columns children.
michael@0: // computedColumnCount are the number of columns as determined by the number
michael@0: // of rows children.
michael@0: // We can use this information to see how many extra columns or rows we need.
michael@0: // This can happen if there are are more children in a row that number of columns
michael@0: // defined. Example:
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: // computedColumnCount = 2 // for the 2 buttons in the row tag
michael@0: // computedRowCount = 0 // there is nothing in the column tag
michael@0: // mColumnCount = 1 // one column defined
michael@0: // mRowCount = 1 // one row defined
michael@0: //
michael@0: // So in this case we need to make 1 extra column.
michael@0: //
michael@0:
michael@0: // Make sure to update mExtraColumnCount no matter what, since it might
michael@0: // happen that we now have as many columns as are defined, and we wouldn't
michael@0: // want to have a positive mExtraColumnCount hanging about in that case!
michael@0: mExtraColumnCount = computedColumnCount - columnCount;
michael@0: if (computedColumnCount > columnCount) {
michael@0: columnCount = computedColumnCount;
michael@0: }
michael@0:
michael@0: // Same for rows.
michael@0: mExtraRowCount = computedRowCount - rowCount;
michael@0: if (computedRowCount > rowCount) {
michael@0: rowCount = computedRowCount;
michael@0: }
michael@0:
michael@0: // build and poplulate row and columns arrays
michael@0: BuildRows(mRowsBox, rowCount, &mRows, true);
michael@0: BuildRows(mColumnsBox, columnCount, &mColumns, false);
michael@0:
michael@0: // build and populate the cell map
michael@0: mCellMap = BuildCellMap(rowCount, columnCount);
michael@0:
michael@0: mRowCount = rowCount;
michael@0: mColumnCount = columnCount;
michael@0:
michael@0: // populate the cell map from column and row children
michael@0: PopulateCellMap(mRows, mColumns, mRowCount, mColumnCount, true);
michael@0: PopulateCellMap(mColumns, mRows, mColumnCount, mRowCount, false);
michael@0: }
michael@0:
michael@0: void
michael@0: nsGrid::FreeMap()
michael@0: {
michael@0: if (mRows)
michael@0: delete[] mRows;
michael@0:
michael@0: if (mColumns)
michael@0: delete[] mColumns;
michael@0:
michael@0: if (mCellMap)
michael@0: delete[] mCellMap;
michael@0:
michael@0: mRows = nullptr;
michael@0: mColumns = nullptr;
michael@0: mCellMap = nullptr;
michael@0: mColumnCount = 0;
michael@0: mRowCount = 0;
michael@0: mExtraColumnCount = 0;
michael@0: mExtraRowCount = 0;
michael@0: mRowsBox = nullptr;
michael@0: mColumnsBox = nullptr;
michael@0: }
michael@0:
michael@0: /**
michael@0: * finds the first and tags in the tag
michael@0: */
michael@0: void
michael@0: nsGrid::FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns)
michael@0: {
michael@0: *aRows = nullptr;
michael@0: *aColumns = nullptr;
michael@0:
michael@0: // find the boxes that contain our rows and columns
michael@0: nsIFrame* child = nullptr;
michael@0: // if we have then mBox will be null (bug 125689)
michael@0: if (mBox)
michael@0: child = mBox->GetChildBox();
michael@0:
michael@0: while(child)
michael@0: {
michael@0: nsIFrame* oldBox = child;
michael@0: nsIScrollableFrame *scrollFrame = do_QueryFrame(child);
michael@0: if (scrollFrame) {
michael@0: nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
michael@0: NS_ASSERTION(scrolledFrame,"Error no scroll frame!!");
michael@0: child = do_QueryFrame(scrolledFrame);
michael@0: }
michael@0:
michael@0: nsCOMPtr monument = GetPartFromBox(child);
michael@0: if (monument)
michael@0: {
michael@0: nsGridRowGroupLayout* rowGroup = monument->CastToRowGroupLayout();
michael@0: if (rowGroup) {
michael@0: bool isHorizontal = !nsSprocketLayout::IsHorizontal(child);
michael@0: if (isHorizontal)
michael@0: *aRows = child;
michael@0: else
michael@0: *aColumns = child;
michael@0:
michael@0: if (*aRows && *aColumns)
michael@0: return;
michael@0: }
michael@0: }
michael@0:
michael@0: if (scrollFrame) {
michael@0: child = oldBox;
michael@0: }
michael@0:
michael@0: child = child->GetNextBox();
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Count the number of rows and columns in the given box. aRowCount well become the actual number
michael@0: * rows defined in the xul. aComputedColumnCount will become the number of columns by counting the number
michael@0: * of cells in each row.
michael@0: */
michael@0: void
michael@0: nsGrid::CountRowsColumns(nsIFrame* aRowBox, int32_t& aRowCount, int32_t& aComputedColumnCount)
michael@0: {
michael@0: aRowCount = 0;
michael@0: aComputedColumnCount = 0;
michael@0: // get the rowboxes layout manager. Then ask it to do the work for us
michael@0: if (aRowBox) {
michael@0: nsCOMPtr monument = GetPartFromBox(aRowBox);
michael@0: if (monument)
michael@0: monument->CountRowsColumns(aRowBox, aRowCount, aComputedColumnCount);
michael@0: }
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Given the number of rows create nsGridRow objects for them and full them out.
michael@0: */
michael@0: void
michael@0: nsGrid::BuildRows(nsIFrame* aBox, int32_t aRowCount, nsGridRow** aRows, bool aIsHorizontal)
michael@0: {
michael@0: // if no rows then return null
michael@0: if (aRowCount == 0) {
michael@0:
michael@0: // make sure we free up the memory.
michael@0: if (*aRows)
michael@0: delete[] (*aRows);
michael@0:
michael@0: *aRows = nullptr;
michael@0: return;
michael@0: }
michael@0:
michael@0: // create the array
michael@0: nsGridRow* row;
michael@0:
michael@0: // only create new rows if we have to. Reuse old rows.
michael@0: if (aIsHorizontal)
michael@0: {
michael@0: if (aRowCount > mRowCount) {
michael@0: delete[] mRows;
michael@0: row = new nsGridRow[aRowCount];
michael@0: } else {
michael@0: for (int32_t i=0; i < mRowCount; i++)
michael@0: mRows[i].Init(nullptr, false);
michael@0:
michael@0: row = mRows;
michael@0: }
michael@0: } else {
michael@0: if (aRowCount > mColumnCount) {
michael@0: delete[] mColumns;
michael@0: row = new nsGridRow[aRowCount];
michael@0: } else {
michael@0: for (int32_t i=0; i < mColumnCount; i++)
michael@0: mColumns[i].Init(nullptr, false);
michael@0:
michael@0: row = mColumns;
michael@0: }
michael@0: }
michael@0:
michael@0: // populate it if we can. If not it will contain only dynamic columns
michael@0: if (aBox)
michael@0: {
michael@0: nsCOMPtr monument = GetPartFromBox(aBox);
michael@0: if (monument) {
michael@0: monument->BuildRows(aBox, row);
michael@0: }
michael@0: }
michael@0:
michael@0: *aRows = row;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Given the number of rows and columns. Build a cellmap
michael@0: */
michael@0: nsGridCell*
michael@0: nsGrid::BuildCellMap(int32_t aRows, int32_t aColumns)
michael@0: {
michael@0: int32_t size = aRows*aColumns;
michael@0: int32_t oldsize = mRowCount*mColumnCount;
michael@0: if (size == 0) {
michael@0: delete[] mCellMap;
michael@0: }
michael@0: else {
michael@0: if (size > oldsize) {
michael@0: delete[] mCellMap;
michael@0: return new nsGridCell[size];
michael@0: } else {
michael@0: // clear out cellmap
michael@0: for (int32_t i=0; i < oldsize; i++)
michael@0: {
michael@0: mCellMap[i].SetBoxInRow(nullptr);
michael@0: mCellMap[i].SetBoxInColumn(nullptr);
michael@0: }
michael@0: return mCellMap;
michael@0: }
michael@0: }
michael@0: return nullptr;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Run through all the cells in the rows and columns and populate then with 2 cells. One from the row and one
michael@0: * from the column
michael@0: */
michael@0: void
michael@0: nsGrid::PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns, int32_t aRowCount, int32_t aColumnCount, bool aIsHorizontal)
michael@0: {
michael@0: if (!aRows)
michael@0: return;
michael@0:
michael@0: // look through the columns
michael@0: int32_t j = 0;
michael@0:
michael@0: for(int32_t i=0; i < aRowCount; i++)
michael@0: {
michael@0: nsIFrame* child = nullptr;
michael@0: nsGridRow* row = &aRows[i];
michael@0:
michael@0: // skip bogus rows. They have no cells
michael@0: if (row->mIsBogus)
michael@0: continue;
michael@0:
michael@0: child = row->mBox;
michael@0: if (child) {
michael@0: child = child->GetChildBox();
michael@0:
michael@0: j = 0;
michael@0:
michael@0: while(child && j < aColumnCount)
michael@0: {
michael@0: // skip bogus column. They have no cells
michael@0: nsGridRow* column = &aColumns[j];
michael@0: if (column->mIsBogus)
michael@0: {
michael@0: j++;
michael@0: continue;
michael@0: }
michael@0:
michael@0: if (aIsHorizontal)
michael@0: GetCellAt(j,i)->SetBoxInRow(child);
michael@0: else
michael@0: GetCellAt(i,j)->SetBoxInColumn(child);
michael@0:
michael@0: child = child->GetNextBox();
michael@0:
michael@0: j++;
michael@0: }
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * Run through the rows in the given box and mark them dirty so they
michael@0: * will get recalculated and get a layout.
michael@0: */
michael@0: void
michael@0: nsGrid::DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState)
michael@0: {
michael@0: // make sure we prevent others from dirtying things.
michael@0: mMarkingDirty = true;
michael@0:
michael@0: // if the box is a grid part have it recursively hand it.
michael@0: if (aRowBox) {
michael@0: nsCOMPtr part = GetPartFromBox(aRowBox);
michael@0: if (part)
michael@0: part->DirtyRows(aRowBox, aState);
michael@0: }
michael@0:
michael@0: mMarkingDirty = false;
michael@0: }
michael@0:
michael@0: nsGridRow*
michael@0: nsGrid::GetColumnAt(int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: return GetRowAt(aIndex, !aIsHorizontal);
michael@0: }
michael@0:
michael@0: nsGridRow*
michael@0: nsGrid::GetRowAt(int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: if (aIsHorizontal) {
michael@0: NS_ASSERTION(aIndex < mRowCount && aIndex >= 0, "Index out of range");
michael@0: return &mRows[aIndex];
michael@0: } else {
michael@0: NS_ASSERTION(aIndex < mColumnCount && aIndex >= 0, "Index out of range");
michael@0: return &mColumns[aIndex];
michael@0: }
michael@0: }
michael@0:
michael@0: nsGridCell*
michael@0: nsGrid::GetCellAt(int32_t aX, int32_t aY)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: NS_ASSERTION(aY < mRowCount && aY >= 0, "Index out of range");
michael@0: NS_ASSERTION(aX < mColumnCount && aX >= 0, "Index out of range");
michael@0: return &mCellMap[aY*mColumnCount+aX];
michael@0: }
michael@0:
michael@0: int32_t
michael@0: nsGrid::GetExtraColumnCount(bool aIsHorizontal)
michael@0: {
michael@0: return GetExtraRowCount(!aIsHorizontal);
michael@0: }
michael@0:
michael@0: int32_t
michael@0: nsGrid::GetExtraRowCount(bool aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: if (aIsHorizontal)
michael@0: return mExtraRowCount;
michael@0: else
michael@0: return mExtraColumnCount;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * These methods return the preferred, min, max sizes for a given row index.
michael@0: * aIsHorizontal if aIsHorizontal is true. If you pass false you will get the inverse.
michael@0: * As if you called GetPrefColumnSize(aState, index, aPref)
michael@0: */
michael@0: nsSize
michael@0: nsGrid::GetPrefRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal)
michael@0: {
michael@0: nsSize size(0,0);
michael@0: if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal)))
michael@0: return size;
michael@0:
michael@0: nscoord height = GetPrefRowHeight(aState, aRowIndex, aIsHorizontal);
michael@0: SetLargestSize(size, height, aIsHorizontal);
michael@0:
michael@0: return size;
michael@0: }
michael@0:
michael@0: nsSize
michael@0: nsGrid::GetMinRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal)
michael@0: {
michael@0: nsSize size(0,0);
michael@0: if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal)))
michael@0: return size;
michael@0:
michael@0: nscoord height = GetMinRowHeight(aState, aRowIndex, aIsHorizontal);
michael@0: SetLargestSize(size, height, aIsHorizontal);
michael@0:
michael@0: return size;
michael@0: }
michael@0:
michael@0: nsSize
michael@0: nsGrid::GetMaxRowSize(nsBoxLayoutState& aState, int32_t aRowIndex, bool aIsHorizontal)
michael@0: {
michael@0: nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
michael@0: if (!(aRowIndex >=0 && aRowIndex < GetRowCount(aIsHorizontal)))
michael@0: return size;
michael@0:
michael@0: nscoord height = GetMaxRowHeight(aState, aRowIndex, aIsHorizontal);
michael@0: SetSmallestSize(size, height, aIsHorizontal);
michael@0:
michael@0: return size;
michael@0: }
michael@0:
michael@0: // static
michael@0: nsIGridPart*
michael@0: nsGrid::GetPartFromBox(nsIFrame* aBox)
michael@0: {
michael@0: if (!aBox)
michael@0: return nullptr;
michael@0:
michael@0: nsBoxLayout* layout = aBox->GetLayoutManager();
michael@0: return layout ? layout->AsGridPart() : nullptr;
michael@0: }
michael@0:
michael@0: nsMargin
michael@0: nsGrid::GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal)
michael@0: {
michael@0: nsMargin margin(0,0,0,0);
michael@0: // walk the boxes parent chain getting the border/padding/margin of our parent rows
michael@0:
michael@0: // first get the layour manager
michael@0: nsIGridPart* part = GetPartFromBox(aBox);
michael@0: if (part)
michael@0: margin = part->GetTotalMargin(aBox, aIsHorizontal);
michael@0:
michael@0: return margin;
michael@0: }
michael@0:
michael@0: /**
michael@0: * The first and last rows can be affected by tags with borders or margin
michael@0: * gets first and last rows and their indexes.
michael@0: * If it fails because there are no rows then:
michael@0: * FirstRow is nullptr
michael@0: * LastRow is nullptr
michael@0: * aFirstIndex = -1
michael@0: * aLastIndex = -1
michael@0: */
michael@0: void
michael@0: nsGrid::GetFirstAndLastRow(nsBoxLayoutState& aState,
michael@0: int32_t& aFirstIndex,
michael@0: int32_t& aLastIndex,
michael@0: nsGridRow*& aFirstRow,
michael@0: nsGridRow*& aLastRow,
michael@0: bool aIsHorizontal)
michael@0: {
michael@0: aFirstRow = nullptr;
michael@0: aLastRow = nullptr;
michael@0: aFirstIndex = -1;
michael@0: aLastIndex = -1;
michael@0:
michael@0: int32_t count = GetRowCount(aIsHorizontal);
michael@0:
michael@0: if (count == 0)
michael@0: return;
michael@0:
michael@0:
michael@0: // We could have collapsed columns either before or after our index.
michael@0: // they should not count. So if we are the 5th row and the first 4 are
michael@0: // collaped we become the first row. Or if we are the 9th row and
michael@0: // 10 up to the last row are collapsed we then become the last.
michael@0:
michael@0: // see if we are first
michael@0: int32_t i;
michael@0: for (i=0; i < count; i++)
michael@0: {
michael@0: nsGridRow* row = GetRowAt(i,aIsHorizontal);
michael@0: if (!row->IsCollapsed()) {
michael@0: aFirstIndex = i;
michael@0: aFirstRow = row;
michael@0: break;
michael@0: }
michael@0: }
michael@0:
michael@0: // see if we are last
michael@0: for (i=count-1; i >= 0; i--)
michael@0: {
michael@0: nsGridRow* row = GetRowAt(i,aIsHorizontal);
michael@0: if (!row->IsCollapsed()) {
michael@0: aLastIndex = i;
michael@0: aLastRow = row;
michael@0: break;
michael@0: }
michael@0:
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * A row can have a top and bottom offset. Usually this is just the top and bottom border/padding.
michael@0: * However if the row is the first or last it could be affected by the fact a column or columns could
michael@0: * have a top or bottom margin.
michael@0: */
michael@0: void
michael@0: nsGrid::GetRowOffsets(nsBoxLayoutState& aState, int32_t aIndex, nscoord& aTop, nscoord& aBottom, bool aIsHorizontal)
michael@0: {
michael@0:
michael@0: RebuildIfNeeded();
michael@0:
michael@0: nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
michael@0:
michael@0: if (row->IsOffsetSet())
michael@0: {
michael@0: aTop = row->mTop;
michael@0: aBottom = row->mBottom;
michael@0: return;
michael@0: }
michael@0:
michael@0: // first get the rows top and bottom border and padding
michael@0: nsIFrame* box = row->GetBox();
michael@0:
michael@0: // add up all the padding
michael@0: nsMargin margin(0,0,0,0);
michael@0: nsMargin border(0,0,0,0);
michael@0: nsMargin padding(0,0,0,0);
michael@0: nsMargin totalBorderPadding(0,0,0,0);
michael@0: nsMargin totalMargin(0,0,0,0);
michael@0:
michael@0: // if there is a box and it's not bogus take its
michael@0: // borders padding into account
michael@0: if (box && !row->mIsBogus)
michael@0: {
michael@0: if (!box->IsCollapsed())
michael@0: {
michael@0: // get real border and padding. GetBorderAndPadding
michael@0: // is redefined on nsGridRowLeafFrame. If we called it here
michael@0: // we would be in finite recurson.
michael@0: box->GetBorder(border);
michael@0: box->GetPadding(padding);
michael@0:
michael@0: totalBorderPadding += border;
michael@0: totalBorderPadding += padding;
michael@0: }
michael@0:
michael@0: // if we are the first or last row
michael@0: // take into account tags around us
michael@0: // that could have borders or margins.
michael@0: // fortunately they only affect the first
michael@0: // and last row inside the tag
michael@0:
michael@0: totalMargin = GetBoxTotalMargin(box, aIsHorizontal);
michael@0: }
michael@0:
michael@0: if (aIsHorizontal) {
michael@0: row->mTop = totalBorderPadding.top;
michael@0: row->mBottom = totalBorderPadding.bottom;
michael@0: row->mTopMargin = totalMargin.top;
michael@0: row->mBottomMargin = totalMargin.bottom;
michael@0: } else {
michael@0: row->mTop = totalBorderPadding.left;
michael@0: row->mBottom = totalBorderPadding.right;
michael@0: row->mTopMargin = totalMargin.left;
michael@0: row->mBottomMargin = totalMargin.right;
michael@0: }
michael@0:
michael@0: // if we are the first or last row take into account the top and bottom borders
michael@0: // of each columns.
michael@0:
michael@0: // If we are the first row then get the largest top border/padding in
michael@0: // our columns. If that's larger than the rows top border/padding use it.
michael@0:
michael@0: // If we are the last row then get the largest bottom border/padding in
michael@0: // our columns. If that's larger than the rows bottom border/padding use it.
michael@0: int32_t firstIndex = 0;
michael@0: int32_t lastIndex = 0;
michael@0: nsGridRow* firstRow = nullptr;
michael@0: nsGridRow* lastRow = nullptr;
michael@0: GetFirstAndLastRow(aState, firstIndex, lastIndex, firstRow, lastRow, aIsHorizontal);
michael@0:
michael@0: if (aIndex == firstIndex || aIndex == lastIndex) {
michael@0: nscoord maxTop = 0;
michael@0: nscoord maxBottom = 0;
michael@0:
michael@0: // run through the columns. Look at each column
michael@0: // pick the largest top border or bottom border
michael@0: int32_t count = GetColumnCount(aIsHorizontal);
michael@0:
michael@0: for (int32_t i=0; i < count; i++)
michael@0: {
michael@0: nsMargin totalChildBorderPadding(0,0,0,0);
michael@0:
michael@0: nsGridRow* column = GetColumnAt(i,aIsHorizontal);
michael@0: nsIFrame* box = column->GetBox();
michael@0:
michael@0: if (box)
michael@0: {
michael@0: // ignore collapsed children
michael@0: if (!box->IsCollapsed())
michael@0: {
michael@0: // include the margin of the columns. To the row
michael@0: // at this point border/padding and margins all added
michael@0: // up to more needed space.
michael@0: margin = GetBoxTotalMargin(box, !aIsHorizontal);
michael@0: // get real border and padding. GetBorderAndPadding
michael@0: // is redefined on nsGridRowLeafFrame. If we called it here
michael@0: // we would be in finite recurson.
michael@0: box->GetBorder(border);
michael@0: box->GetPadding(padding);
michael@0: totalChildBorderPadding += border;
michael@0: totalChildBorderPadding += padding;
michael@0: totalChildBorderPadding += margin;
michael@0: }
michael@0:
michael@0: nscoord top;
michael@0: nscoord bottom;
michael@0:
michael@0: // pick the largest top margin
michael@0: if (aIndex == firstIndex) {
michael@0: if (aIsHorizontal) {
michael@0: top = totalChildBorderPadding.top;
michael@0: } else {
michael@0: top = totalChildBorderPadding.left;
michael@0: }
michael@0: if (top > maxTop)
michael@0: maxTop = top;
michael@0: }
michael@0:
michael@0: // pick the largest bottom margin
michael@0: if (aIndex == lastIndex) {
michael@0: if (aIsHorizontal) {
michael@0: bottom = totalChildBorderPadding.bottom;
michael@0: } else {
michael@0: bottom = totalChildBorderPadding.right;
michael@0: }
michael@0: if (bottom > maxBottom)
michael@0: maxBottom = bottom;
michael@0: }
michael@0:
michael@0: }
michael@0:
michael@0: // If the biggest top border/padding the columns is larger than this rows top border/padding
michael@0: // the use it.
michael@0: if (aIndex == firstIndex) {
michael@0: if (maxTop > (row->mTop + row->mTopMargin))
michael@0: row->mTop = maxTop - row->mTopMargin;
michael@0: }
michael@0:
michael@0: // If the biggest bottom border/padding the columns is larger than this rows bottom border/padding
michael@0: // the use it.
michael@0: if (aIndex == lastIndex) {
michael@0: if (maxBottom > (row->mBottom + row->mBottomMargin))
michael@0: row->mBottom = maxBottom - row->mBottomMargin;
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: aTop = row->mTop;
michael@0: aBottom = row->mBottom;
michael@0: }
michael@0:
michael@0: /**
michael@0: * These methods return the preferred, min, max coord for a given row index if
michael@0: * aIsHorizontal is true. If you pass false you will get the inverse.
michael@0: * As if you called GetPrefColumnHeight(aState, index, aPref).
michael@0: */
michael@0: nscoord
michael@0: nsGrid::GetPrefRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
michael@0:
michael@0: if (row->IsCollapsed())
michael@0: return 0;
michael@0:
michael@0: if (row->IsPrefSet())
michael@0: return row->mPref;
michael@0:
michael@0: nsIFrame* box = row->mBox;
michael@0:
michael@0: // set in CSS?
michael@0: if (box)
michael@0: {
michael@0: bool widthSet, heightSet;
michael@0: nsSize cssSize(-1, -1);
michael@0: nsIFrame::AddCSSPrefSize(box, cssSize, widthSet, heightSet);
michael@0:
michael@0: row->mPref = GET_HEIGHT(cssSize, aIsHorizontal);
michael@0:
michael@0: // yep do nothing.
michael@0: if (row->mPref != -1)
michael@0: return row->mPref;
michael@0: }
michael@0:
michael@0: // get the offsets so they are cached.
michael@0: nscoord top;
michael@0: nscoord bottom;
michael@0: GetRowOffsets(aState, aIndex, top, bottom, aIsHorizontal);
michael@0:
michael@0: // is the row bogus? If so then just ask it for its size
michael@0: // it should not be affected by cells in the grid.
michael@0: if (row->mIsBogus)
michael@0: {
michael@0: nsSize size(0,0);
michael@0: if (box)
michael@0: {
michael@0: size = box->GetPrefSize(aState);
michael@0: nsBox::AddMargin(box, size);
michael@0: nsGridLayout2::AddOffset(aState, box, size);
michael@0: }
michael@0:
michael@0: row->mPref = GET_HEIGHT(size, aIsHorizontal);
michael@0: return row->mPref;
michael@0: }
michael@0:
michael@0: nsSize size(0,0);
michael@0:
michael@0: nsGridCell* child;
michael@0:
michael@0: int32_t count = GetColumnCount(aIsHorizontal);
michael@0:
michael@0: for (int32_t i=0; i < count; i++)
michael@0: {
michael@0: if (aIsHorizontal)
michael@0: child = GetCellAt(i,aIndex);
michael@0: else
michael@0: child = GetCellAt(aIndex,i);
michael@0:
michael@0: // ignore collapsed children
michael@0: if (!child->IsCollapsed())
michael@0: {
michael@0: nsSize childSize = child->GetPrefSize(aState);
michael@0:
michael@0: nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
michael@0: }
michael@0: }
michael@0:
michael@0: row->mPref = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
michael@0:
michael@0: return row->mPref;
michael@0: }
michael@0:
michael@0: nscoord
michael@0: nsGrid::GetMinRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
michael@0:
michael@0: if (row->IsCollapsed())
michael@0: return 0;
michael@0:
michael@0: if (row->IsMinSet())
michael@0: return row->mMin;
michael@0:
michael@0: nsIFrame* box = row->mBox;
michael@0:
michael@0: // set in CSS?
michael@0: if (box) {
michael@0: bool widthSet, heightSet;
michael@0: nsSize cssSize(-1, -1);
michael@0: nsIFrame::AddCSSMinSize(aState, box, cssSize, widthSet, heightSet);
michael@0:
michael@0: row->mMin = GET_HEIGHT(cssSize, aIsHorizontal);
michael@0:
michael@0: // yep do nothing.
michael@0: if (row->mMin != -1)
michael@0: return row->mMin;
michael@0: }
michael@0:
michael@0: // get the offsets so they are cached.
michael@0: nscoord top;
michael@0: nscoord bottom;
michael@0: GetRowOffsets(aState, aIndex, top, bottom, aIsHorizontal);
michael@0:
michael@0: // is the row bogus? If so then just ask it for its size
michael@0: // it should not be affected by cells in the grid.
michael@0: if (row->mIsBogus)
michael@0: {
michael@0: nsSize size(0,0);
michael@0: if (box) {
michael@0: size = box->GetPrefSize(aState);
michael@0: nsBox::AddMargin(box, size);
michael@0: nsGridLayout2::AddOffset(aState, box, size);
michael@0: }
michael@0:
michael@0: row->mMin = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
michael@0: return row->mMin;
michael@0: }
michael@0:
michael@0: nsSize size(0,0);
michael@0:
michael@0: nsGridCell* child;
michael@0:
michael@0: int32_t count = GetColumnCount(aIsHorizontal);
michael@0:
michael@0: for (int32_t i=0; i < count; i++)
michael@0: {
michael@0: if (aIsHorizontal)
michael@0: child = GetCellAt(i,aIndex);
michael@0: else
michael@0: child = GetCellAt(aIndex,i);
michael@0:
michael@0: // ignore collapsed children
michael@0: if (!child->IsCollapsed())
michael@0: {
michael@0: nsSize childSize = child->GetMinSize(aState);
michael@0:
michael@0: nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
michael@0: }
michael@0: }
michael@0:
michael@0: row->mMin = GET_HEIGHT(size, aIsHorizontal);
michael@0:
michael@0: return row->mMin;
michael@0: }
michael@0:
michael@0: nscoord
michael@0: nsGrid::GetMaxRowHeight(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
michael@0:
michael@0: if (row->IsCollapsed())
michael@0: return 0;
michael@0:
michael@0: if (row->IsMaxSet())
michael@0: return row->mMax;
michael@0:
michael@0: nsIFrame* box = row->mBox;
michael@0:
michael@0: // set in CSS?
michael@0: if (box) {
michael@0: bool widthSet, heightSet;
michael@0: nsSize cssSize(-1, -1);
michael@0: nsIFrame::AddCSSMaxSize(box, cssSize, widthSet, heightSet);
michael@0:
michael@0: row->mMax = GET_HEIGHT(cssSize, aIsHorizontal);
michael@0:
michael@0: // yep do nothing.
michael@0: if (row->mMax != -1)
michael@0: return row->mMax;
michael@0: }
michael@0:
michael@0: // get the offsets so they are cached.
michael@0: nscoord top;
michael@0: nscoord bottom;
michael@0: GetRowOffsets(aState, aIndex, top, bottom, aIsHorizontal);
michael@0:
michael@0: // is the row bogus? If so then just ask it for its size
michael@0: // it should not be affected by cells in the grid.
michael@0: if (row->mIsBogus)
michael@0: {
michael@0: nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
michael@0: if (box) {
michael@0: size = box->GetPrefSize(aState);
michael@0: nsBox::AddMargin(box, size);
michael@0: nsGridLayout2::AddOffset(aState, box, size);
michael@0: }
michael@0:
michael@0: row->mMax = GET_HEIGHT(size, aIsHorizontal);
michael@0: return row->mMax;
michael@0: }
michael@0:
michael@0: nsSize size(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
michael@0:
michael@0: nsGridCell* child;
michael@0:
michael@0: int32_t count = GetColumnCount(aIsHorizontal);
michael@0:
michael@0: for (int32_t i=0; i < count; i++)
michael@0: {
michael@0: if (aIsHorizontal)
michael@0: child = GetCellAt(i,aIndex);
michael@0: else
michael@0: child = GetCellAt(aIndex,i);
michael@0:
michael@0: // ignore collapsed children
michael@0: if (!child->IsCollapsed())
michael@0: {
michael@0: nsSize min = child->GetMinSize(aState);
michael@0: nsSize childSize = nsBox::BoundsCheckMinMax(min, child->GetMaxSize(aState));
michael@0: nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
michael@0: }
michael@0: }
michael@0:
michael@0: row->mMax = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
michael@0:
michael@0: return row->mMax;
michael@0: }
michael@0:
michael@0: bool
michael@0: nsGrid::IsGrid(nsIFrame* aBox)
michael@0: {
michael@0: nsIGridPart* part = GetPartFromBox(aBox);
michael@0: if (!part)
michael@0: return false;
michael@0:
michael@0: nsGridLayout2* grid = part->CastToGridLayout();
michael@0:
michael@0: if (grid)
michael@0: return true;
michael@0:
michael@0: return false;
michael@0: }
michael@0:
michael@0: /**
michael@0: * This get the flexibilty of the row at aIndex. It's not trivial. There are a few
michael@0: * things we need to look at. Specifically we need to see if any or
michael@0: * tags are around us. Their flexibilty will affect ours.
michael@0: */
michael@0: nscoord
michael@0: nsGrid::GetRowFlex(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
michael@0:
michael@0: if (row->IsFlexSet())
michael@0: return row->mFlex;
michael@0:
michael@0: nsIFrame* box = row->mBox;
michael@0: row->mFlex = 0;
michael@0:
michael@0: if (box) {
michael@0:
michael@0: // We need our flex but a inflexible row could be around us. If so
michael@0: // neither are we. However if its the row tag just inside the grid it won't
michael@0: // affect us. We need to do this for this case:
michael@0: //
michael@0: //
michael@0: // // this is not flexible. So our children should not be flexible
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: // or..
michael@0: //
michael@0: //
michael@0: //
michael@0: // // this is not flexible. So our children should not be flexible
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0:
michael@0:
michael@0: // So here is how it looks
michael@0: //
michael@0: //
michael@0: // // parentsParent
michael@0: // // parent
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0: //
michael@0:
michael@0: // so the answer is simple: 1) Walk our parent chain. 2) If we find
michael@0: // someone who is not flexible and they aren't the rows immediately in
michael@0: // the grid. 3) Then we are not flexible
michael@0:
michael@0: box = GetScrollBox(box);
michael@0: nsIFrame* parent = box->GetParentBox();
michael@0: nsIFrame* parentsParent=nullptr;
michael@0:
michael@0: while(parent)
michael@0: {
michael@0: parent = GetScrollBox(parent);
michael@0: parentsParent = parent->GetParentBox();
michael@0:
michael@0: // if our parents parent is not a grid
michael@0: // the get its flex. If its 0 then we are
michael@0: // not flexible.
michael@0: if (parentsParent) {
michael@0: if (!IsGrid(parentsParent)) {
michael@0: nscoord flex = parent->GetFlex(aState);
michael@0: nsIFrame::AddCSSFlex(aState, parent, flex);
michael@0: if (flex == 0) {
michael@0: row->mFlex = 0;
michael@0: return row->mFlex;
michael@0: }
michael@0: } else
michael@0: break;
michael@0: }
michael@0:
michael@0: parent = parentsParent;
michael@0: }
michael@0:
michael@0: // get the row flex.
michael@0: row->mFlex = box->GetFlex(aState);
michael@0: nsIFrame::AddCSSFlex(aState, box, row->mFlex);
michael@0: }
michael@0:
michael@0: return row->mFlex;
michael@0: }
michael@0:
michael@0: void
michael@0: nsGrid::SetLargestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal)
michael@0: {
michael@0: if (aIsHorizontal) {
michael@0: if (aSize.height < aHeight)
michael@0: aSize.height = aHeight;
michael@0: } else {
michael@0: if (aSize.width < aHeight)
michael@0: aSize.width = aHeight;
michael@0: }
michael@0: }
michael@0:
michael@0: void
michael@0: nsGrid::SetSmallestSize(nsSize& aSize, nscoord aHeight, bool aIsHorizontal)
michael@0: {
michael@0: if (aIsHorizontal) {
michael@0: if (aSize.height > aHeight)
michael@0: aSize.height = aHeight;
michael@0: } else {
michael@0: if (aSize.width < aHeight)
michael@0: aSize.width = aHeight;
michael@0: }
michael@0: }
michael@0:
michael@0: int32_t
michael@0: nsGrid::GetRowCount(int32_t aIsHorizontal)
michael@0: {
michael@0: RebuildIfNeeded();
michael@0:
michael@0: if (aIsHorizontal)
michael@0: return mRowCount;
michael@0: else
michael@0: return mColumnCount;
michael@0: }
michael@0:
michael@0: int32_t
michael@0: nsGrid::GetColumnCount(int32_t aIsHorizontal)
michael@0: {
michael@0: return GetRowCount(!aIsHorizontal);
michael@0: }
michael@0:
michael@0: /*
michael@0: * A cell in the given row or columns at the given index has had a child added or removed
michael@0: */
michael@0: void
michael@0: nsGrid::CellAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: // TBD see if the cell will fit in our current row. If it will
michael@0: // just add it in.
michael@0: // but for now rebuild everything.
michael@0: if (mMarkingDirty)
michael@0: return;
michael@0:
michael@0: NeedsRebuild(aState);
michael@0: }
michael@0:
michael@0: /**
michael@0: * A row or columns at the given index had been added or removed
michael@0: */
michael@0: void
michael@0: nsGrid::RowAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex, bool aIsHorizontal)
michael@0: {
michael@0: // TBD see if we have extra room in the table and just add the new row in
michael@0: // for now rebuild the world
michael@0: if (mMarkingDirty)
michael@0: return;
michael@0:
michael@0: NeedsRebuild(aState);
michael@0: }
michael@0:
michael@0: /*
michael@0: * Scrollframes are tranparent. If this is given a scrollframe is will return the
michael@0: * frame inside. If there is no scrollframe it does nothing.
michael@0: */
michael@0: nsIFrame*
michael@0: nsGrid::GetScrolledBox(nsIFrame* aChild)
michael@0: {
michael@0: // first see if it is a scrollframe. If so walk down into it and get the scrolled child
michael@0: nsIScrollableFrame *scrollFrame = do_QueryFrame(aChild);
michael@0: if (scrollFrame) {
michael@0: nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
michael@0: NS_ASSERTION(scrolledFrame,"Error no scroll frame!!");
michael@0: return scrolledFrame;
michael@0: }
michael@0:
michael@0: return aChild;
michael@0: }
michael@0:
michael@0: /*
michael@0: * Scrollframes are tranparent. If this is given a child in a scrollframe is will return the
michael@0: * scrollframe ourside it. If there is no scrollframe it does nothing.
michael@0: */
michael@0: nsIFrame*
michael@0: nsGrid::GetScrollBox(nsIFrame* aChild)
michael@0: {
michael@0: if (!aChild)
michael@0: return nullptr;
michael@0:
michael@0: // get parent
michael@0: nsIFrame* parent = aChild->GetParentBox();
michael@0:
michael@0: // walk up until we find a scrollframe or a part
michael@0: // if it's a scrollframe return it.
michael@0: // if it's a parent then the child passed does not
michael@0: // have a scroll frame immediately wrapped around it.
michael@0: while (parent) {
michael@0: nsIScrollableFrame *scrollFrame = do_QueryFrame(parent);
michael@0: // scrollframe? Yep return it.
michael@0: if (scrollFrame)
michael@0: return parent;
michael@0:
michael@0: nsCOMPtr parentGridRow = GetPartFromBox(parent);
michael@0: // if a part then just return the child
michael@0: if (parentGridRow)
michael@0: break;
michael@0:
michael@0: parent = parent->GetParentBox();
michael@0: }
michael@0:
michael@0: return aChild;
michael@0: }
michael@0:
michael@0:
michael@0:
michael@0: #ifdef DEBUG_grid
michael@0: void
michael@0: nsGrid::PrintCellMap()
michael@0: {
michael@0:
michael@0: printf("-----Columns------\n");
michael@0: for (int x=0; x < mColumnCount; x++)
michael@0: {
michael@0:
michael@0: nsGridRow* column = GetColumnAt(x);
michael@0: printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax);
michael@0: }
michael@0:
michael@0: printf("\n-----Rows------\n");
michael@0: for (x=0; x < mRowCount; x++)
michael@0: {
michael@0: nsGridRow* column = GetRowAt(x);
michael@0: printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin, column->mMax);
michael@0: }
michael@0:
michael@0: printf("\n");
michael@0:
michael@0: }
michael@0: #endif