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