1 /*
2  * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package javax.swing.text.html;
26 
27 import java.awt.*;
28 import java.util.BitSet;
29 import java.util.Vector;
30 import java.util.Arrays;
31 import javax.swing.SizeRequirements;
32 import javax.swing.event.DocumentEvent;
33 
34 import javax.swing.text.*;
35 
36 /**
37  * HTML table view.
38  *
39  * @author  Timothy Prinzing
40  * @see     View
41  */
42 /*public*/ class TableView extends BoxView implements ViewFactory {
43 
44     /**
45      * Constructs a TableView for the given element.
46      *
47      * @param elem the element that this view is responsible for
48      */
TableView(Element elem)49     public TableView(Element elem) {
50         super(elem, View.Y_AXIS);
51         rows = new Vector<RowView>();
52         gridValid = false;
53         captionIndex = -1;
54         totalColumnRequirements = new SizeRequirements();
55     }
56 
57     /**
58      * Creates a new table row.
59      *
60      * @param elem an element
61      * @return the row
62      */
createTableRow(Element elem)63     protected RowView createTableRow(Element elem) {
64         // PENDING(prinz) need to add support for some of the other
65         // elements, but for now just ignore anything that is not
66         // a TR.
67         Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
68         if (o == HTML.Tag.TR) {
69             return new RowView(elem);
70         }
71         return null;
72     }
73 
74     /**
75      * The number of columns in the table.
76      */
getColumnCount()77     public int getColumnCount() {
78         return columnSpans.length;
79     }
80 
81     /**
82      * Fetches the span (width) of the given column.
83      * This is used by the nested cells to query the
84      * sizes of grid locations outside of themselves.
85      */
getColumnSpan(int col)86     public int getColumnSpan(int col) {
87         if (col < columnSpans.length) {
88             return columnSpans[col];
89         }
90         return 0;
91     }
92 
93     /**
94      * The number of rows in the table.
95      */
getRowCount()96     public int getRowCount() {
97         return rows.size();
98     }
99 
100     /**
101      * Fetch the span of multiple rows.  This includes
102      * the border area.
103      */
getMultiRowSpan(int row0, int row1)104     public int getMultiRowSpan(int row0, int row1) {
105         RowView rv0 = getRow(row0);
106         RowView rv1 = getRow(row1);
107         if ((rv0 != null) && (rv1 != null)) {
108             int index0 = rv0.viewIndex;
109             int index1 = rv1.viewIndex;
110             int span = getOffset(Y_AXIS, index1) - getOffset(Y_AXIS, index0) +
111                 getSpan(Y_AXIS, index1);
112             return span;
113         }
114         return 0;
115     }
116 
117     /**
118      * Fetches the span (height) of the given row.
119      */
getRowSpan(int row)120     public int getRowSpan(int row) {
121         RowView rv = getRow(row);
122         if (rv != null) {
123             return getSpan(Y_AXIS, rv.viewIndex);
124         }
125         return 0;
126     }
127 
getRow(int row)128     RowView getRow(int row) {
129         if (row < rows.size()) {
130             return rows.elementAt(row);
131         }
132         return null;
133     }
134 
getViewAtPoint(int x, int y, Rectangle alloc)135     protected View getViewAtPoint(int x, int y, Rectangle alloc) {
136         int n = getViewCount();
137         View v;
138         Rectangle allocation = new Rectangle();
139         for (int i = 0; i < n; i++) {
140             allocation.setBounds(alloc);
141             childAllocation(i, allocation);
142             v = getView(i);
143             if (v instanceof RowView) {
144                 v = ((RowView)v).findViewAtPoint(x, y, allocation);
145                 if (v != null) {
146                     alloc.setBounds(allocation);
147                     return v;
148                 }
149             }
150         }
151         return super.getViewAtPoint(x, y, alloc);
152     }
153 
154     /**
155      * Determines the number of columns occupied by
156      * the table cell represented by given element.
157      */
getColumnsOccupied(View v)158     protected int getColumnsOccupied(View v) {
159         AttributeSet a = v.getElement().getAttributes();
160 
161         if (a.isDefined(HTML.Attribute.COLSPAN)) {
162             String s = (String) a.getAttribute(HTML.Attribute.COLSPAN);
163             if (s != null) {
164                 try {
165                     return Integer.parseInt(s);
166                 } catch (NumberFormatException nfe) {
167                     // fall through to one column
168                 }
169             }
170         }
171 
172         return 1;
173     }
174 
175     /**
176      * Determines the number of rows occupied by
177      * the table cell represented by given element.
178      */
getRowsOccupied(View v)179     protected int getRowsOccupied(View v) {
180         AttributeSet a = v.getElement().getAttributes();
181 
182         if (a.isDefined(HTML.Attribute.ROWSPAN)) {
183             String s = (String) a.getAttribute(HTML.Attribute.ROWSPAN);
184             if (s != null) {
185                 try {
186                     return Integer.parseInt(s);
187                 } catch (NumberFormatException nfe) {
188                     // fall through to one row
189                 }
190             }
191         }
192 
193         return 1;
194     }
195 
invalidateGrid()196     protected void invalidateGrid() {
197         gridValid = false;
198     }
199 
getStyleSheet()200     protected StyleSheet getStyleSheet() {
201         HTMLDocument doc = (HTMLDocument) getDocument();
202         return doc.getStyleSheet();
203     }
204 
205     /**
206      * Update the insets, which contain the caption if there
207      * is a caption.
208      */
updateInsets()209     void updateInsets() {
210         short top = (short) painter.getInset(TOP, this);
211         short bottom = (short) painter.getInset(BOTTOM, this);
212         if (captionIndex != -1) {
213             View caption = getView(captionIndex);
214             short h = (short) caption.getPreferredSpan(Y_AXIS);
215             AttributeSet a = caption.getAttributes();
216             Object align = a.getAttribute(CSS.Attribute.CAPTION_SIDE);
217             if ((align != null) && (align.equals("bottom"))) {
218                 bottom += h;
219             } else {
220                 top += h;
221             }
222         }
223         setInsets(top, (short) painter.getInset(LEFT, this),
224                   bottom, (short) painter.getInset(RIGHT, this));
225     }
226 
227     /**
228      * Update any cached values that come from attributes.
229      */
setPropertiesFromAttributes()230     protected void setPropertiesFromAttributes() {
231         StyleSheet sheet = getStyleSheet();
232         attr = sheet.getViewAttributes(this);
233         painter = sheet.getBoxPainter(attr);
234         if (attr != null) {
235             setInsets((short) painter.getInset(TOP, this),
236                       (short) painter.getInset(LEFT, this),
237                           (short) painter.getInset(BOTTOM, this),
238                       (short) painter.getInset(RIGHT, this));
239 
240             CSS.LengthValue lv = (CSS.LengthValue)
241                 attr.getAttribute(CSS.Attribute.BORDER_SPACING);
242             if (lv != null) {
243                 cellSpacing = (int) lv.getValue();
244             } else {
245                 // Default cell spacing equals 2
246                 cellSpacing = 2;
247             }
248             lv = (CSS.LengthValue)
249                     attr.getAttribute(CSS.Attribute.BORDER_TOP_WIDTH);
250             if (lv != null) {
251                     borderWidth = (int) lv.getValue();
252             } else {
253                     borderWidth = 0;
254             }
255         }
256     }
257 
258     /**
259      * Fill in the grid locations that are placeholders
260      * for multi-column, multi-row, and missing grid
261      * locations.
262      */
updateGrid()263     void updateGrid() {
264         if (! gridValid) {
265             relativeCells = false;
266             multiRowCells = false;
267 
268             // determine which views are table rows and clear out
269             // grid points marked filled.
270             captionIndex = -1;
271             rows.removeAllElements();
272             int n = getViewCount();
273             for (int i = 0; i < n; i++) {
274                 View v = getView(i);
275                 if (v instanceof RowView) {
276                     rows.addElement((RowView) v);
277                     RowView rv = (RowView) v;
278                     rv.clearFilledColumns();
279                     rv.rowIndex = rows.size() - 1;
280                     rv.viewIndex = i;
281                 } else {
282                     Object o = v.getElement().getAttributes().getAttribute(StyleConstants.NameAttribute);
283                     if (o instanceof HTML.Tag) {
284                         HTML.Tag kind = (HTML.Tag) o;
285                         if (kind == HTML.Tag.CAPTION) {
286                             captionIndex = i;
287                         }
288                     }
289                 }
290             }
291 
292             int maxColumns = 0;
293             int nrows = rows.size();
294             for (int row = 0; row < nrows; row++) {
295                 RowView rv = getRow(row);
296                 int col = 0;
297                 for (int cell = 0; cell < rv.getViewCount(); cell++, col++) {
298                     View cv = rv.getView(cell);
299                     if (! relativeCells) {
300                         AttributeSet a = cv.getAttributes();
301                         CSS.LengthValue lv = (CSS.LengthValue)
302                             a.getAttribute(CSS.Attribute.WIDTH);
303                         if ((lv != null) && (lv.isPercentage())) {
304                             relativeCells = true;
305                         }
306                     }
307                     // advance to a free column
308                     for (; rv.isFilled(col); col++);
309                     int rowSpan = getRowsOccupied(cv);
310                     if (rowSpan > 1) {
311                         multiRowCells = true;
312                     }
313                     int colSpan = getColumnsOccupied(cv);
314                     if ((colSpan > 1) || (rowSpan > 1)) {
315                         // fill in the overflow entries for this cell
316                         int rowLimit = row + rowSpan;
317                         int colLimit = col + colSpan;
318                         for (int i = row; i < rowLimit; i++) {
319                             for (int j = col; j < colLimit; j++) {
320                                 if (i != row || j != col) {
321                                     addFill(i, j);
322                                 }
323                             }
324                         }
325                         if (colSpan > 1) {
326                             col += colSpan - 1;
327                         }
328                     }
329                 }
330                 maxColumns = Math.max(maxColumns, col);
331             }
332 
333             // setup the column layout/requirements
334             columnSpans = new int[maxColumns];
335             columnOffsets = new int[maxColumns];
336             columnRequirements = new SizeRequirements[maxColumns];
337             for (int i = 0; i < maxColumns; i++) {
338                 columnRequirements[i] = new SizeRequirements();
339                 columnRequirements[i].maximum = Integer.MAX_VALUE;
340             }
341             gridValid = true;
342         }
343     }
344 
345     /**
346      * Mark a grid location as filled in for a cells overflow.
347      */
addFill(int row, int col)348     void addFill(int row, int col) {
349         RowView rv = getRow(row);
350         if (rv != null) {
351             rv.fillColumn(col);
352         }
353     }
354 
355     /**
356      * Layout the columns to fit within the given target span.
357      *
358      * @param targetSpan the given span for total of all the table
359      *  columns
360      * @param reqs the requirements desired for each column.  This
361      *  is the column maximum of the cells minimum, preferred, and
362      *  maximum requested span
363      * @param spans the return value of how much to allocated to
364      *  each column
365      * @param offsets the return value of the offset from the
366      *  origin for each column
367      * @return the offset from the origin and the span for each column
368      *  in the offsets and spans parameters
369      */
layoutColumns(int targetSpan, int[] offsets, int[] spans, SizeRequirements[] reqs)370     protected void layoutColumns(int targetSpan, int[] offsets, int[] spans,
371                                  SizeRequirements[] reqs) {
372         //clean offsets and spans
373         Arrays.fill(offsets, 0);
374         Arrays.fill(spans, 0);
375         colIterator.setLayoutArrays(offsets, spans, targetSpan);
376         CSS.calculateTiledLayout(colIterator, targetSpan);
377     }
378 
379     /**
380      * Calculate the requirements for each column.  The calculation
381      * is done as two passes over the table.  The table cells that
382      * occupy a single column are scanned first to determine the
383      * maximum of minimum, preferred, and maximum spans along the
384      * give axis.  Table cells that span multiple columns are excluded
385      * from the first pass.  A second pass is made to determine if
386      * the cells that span multiple columns are satisfied.  If the
387      * column requirements are not satisified, the needs of the
388      * multi-column cell is mixed into the existing column requirements.
389      * The calculation of the multi-column distribution is based upon
390      * the proportions of the existing column requirements and taking
391      * into consideration any constraining maximums.
392      */
calculateColumnRequirements(int axis)393     void calculateColumnRequirements(int axis) {
394         // clean columnRequirements
395         for (SizeRequirements req : columnRequirements) {
396             req.minimum = 0;
397             req.preferred = 0;
398             req.maximum = Integer.MAX_VALUE;
399         }
400         Container host = getContainer();
401         if (host != null) {
402             if (host instanceof JTextComponent) {
403                 skipComments = !((JTextComponent)host).isEditable();
404             } else {
405                 skipComments = true;
406             }
407         }
408         // pass 1 - single column cells
409         boolean hasMultiColumn = false;
410         int nrows = getRowCount();
411         for (int i = 0; i < nrows; i++) {
412             RowView row = getRow(i);
413             int col = 0;
414             int ncells = row.getViewCount();
415             for (int cell = 0; cell < ncells; cell++) {
416                 View cv = row.getView(cell);
417                 if (skipComments && !(cv instanceof CellView)) {
418                     continue;
419                 }
420                 for (; row.isFilled(col); col++); // advance to a free column
421                 int rowSpan = getRowsOccupied(cv);
422                 int colSpan = getColumnsOccupied(cv);
423                 if (colSpan == 1) {
424                     checkSingleColumnCell(axis, col, cv);
425                 } else {
426                     hasMultiColumn = true;
427                     col += colSpan - 1;
428                 }
429                 col++;
430             }
431         }
432 
433         // pass 2 - multi-column cells
434         if (hasMultiColumn) {
435             for (int i = 0; i < nrows; i++) {
436                 RowView row = getRow(i);
437                 int col = 0;
438                 int ncells = row.getViewCount();
439                 for (int cell = 0; cell < ncells; cell++) {
440                     View cv = row.getView(cell);
441                     if (skipComments && !(cv instanceof CellView)) {
442                         continue;
443                     }
444                     for (; row.isFilled(col); col++); // advance to a free column
445                     int colSpan = getColumnsOccupied(cv);
446                     if (colSpan > 1) {
447                         checkMultiColumnCell(axis, col, colSpan, cv);
448                         col += colSpan - 1;
449                     }
450                     col++;
451                 }
452             }
453         }
454     }
455 
456     /**
457      * check the requirements of a table cell that spans a single column.
458      */
checkSingleColumnCell(int axis, int col, View v)459     void checkSingleColumnCell(int axis, int col, View v) {
460         SizeRequirements req = columnRequirements[col];
461         req.minimum = Math.max((int) v.getMinimumSpan(axis), req.minimum);
462         req.preferred = Math.max((int) v.getPreferredSpan(axis), req.preferred);
463     }
464 
465     /**
466      * check the requirements of a table cell that spans multiple
467      * columns.
468      */
checkMultiColumnCell(int axis, int col, int ncols, View v)469     void checkMultiColumnCell(int axis, int col, int ncols, View v) {
470         // calculate the totals
471         long min = 0;
472         long pref = 0;
473         long max = 0;
474         for (int i = 0; i < ncols; i++) {
475             SizeRequirements req = columnRequirements[col + i];
476             min += req.minimum;
477             pref += req.preferred;
478             max += req.maximum;
479         }
480 
481         // check if the minimum size needs adjustment.
482         int cmin = (int) v.getMinimumSpan(axis);
483         if (cmin > min) {
484             /*
485              * the columns that this cell spans need adjustment to fit
486              * this table cell.... calculate the adjustments.
487              */
488             SizeRequirements[] reqs = new SizeRequirements[ncols];
489             for (int i = 0; i < ncols; i++) {
490                 reqs[i] = columnRequirements[col + i];
491             }
492             int[] spans = new int[ncols];
493             int[] offsets = new int[ncols];
494             SizeRequirements.calculateTiledPositions(cmin, null, reqs,
495                                                      offsets, spans);
496             // apply the adjustments
497             for (int i = 0; i < ncols; i++) {
498                 SizeRequirements req = reqs[i];
499                 req.minimum = Math.max(spans[i], req.minimum);
500                 req.preferred = Math.max(req.minimum, req.preferred);
501                 req.maximum = Math.max(req.preferred, req.maximum);
502             }
503         }
504 
505         // check if the preferred size needs adjustment.
506         int cpref = (int) v.getPreferredSpan(axis);
507         if (cpref > pref) {
508             /*
509              * the columns that this cell spans need adjustment to fit
510              * this table cell.... calculate the adjustments.
511              */
512             SizeRequirements[] reqs = new SizeRequirements[ncols];
513             for (int i = 0; i < ncols; i++) {
514                 reqs[i] = columnRequirements[col + i];
515             }
516             int[] spans = new int[ncols];
517             int[] offsets = new int[ncols];
518             SizeRequirements.calculateTiledPositions(cpref, null, reqs,
519                                                      offsets, spans);
520             // apply the adjustments
521             for (int i = 0; i < ncols; i++) {
522                 SizeRequirements req = reqs[i];
523                 req.preferred = Math.max(spans[i], req.preferred);
524                 req.maximum = Math.max(req.preferred, req.maximum);
525             }
526         }
527 
528     }
529 
530     // --- BoxView methods -----------------------------------------
531 
532     /**
533      * Calculate the requirements for the minor axis.  This is called by
534      * the superclass whenever the requirements need to be updated (i.e.
535      * a preferenceChanged was messaged through this view).
536      * <p>
537      * This is implemented to calculate the requirements as the sum of the
538      * requirements of the columns and then adjust it if the
539      * CSS width or height attribute is specified and applicable to
540      * the axis.
541      */
calculateMinorAxisRequirements(int axis, SizeRequirements r)542     protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
543         updateGrid();
544 
545         // calculate column requirements for each column
546         calculateColumnRequirements(axis);
547 
548 
549         // the requirements are the sum of the columns.
550         if (r == null) {
551             r = new SizeRequirements();
552         }
553         long min = 0;
554         long pref = 0;
555         int n = columnRequirements.length;
556         for (int i = 0; i < n; i++) {
557             SizeRequirements req = columnRequirements[i];
558             min += req.minimum;
559             pref += req.preferred;
560         }
561         int adjust = (n + 1) * cellSpacing + 2 * borderWidth;
562         min += adjust;
563         pref += adjust;
564         r.minimum = (int) min;
565         r.preferred = (int) pref;
566         r.maximum = (int) pref;
567 
568 
569         AttributeSet attr = getAttributes();
570         CSS.LengthValue cssWidth = (CSS.LengthValue)attr.getAttribute(
571                                                     CSS.Attribute.WIDTH);
572 
573         if (BlockView.spanSetFromAttributes(axis, r, cssWidth, null)) {
574             if (r.minimum < (int)min) {
575                 // The user has requested a smaller size than is needed to
576                 // show the table, override it.
577                 r.maximum = r.minimum = r.preferred = (int) min;
578             }
579         }
580         totalColumnRequirements.minimum = r.minimum;
581         totalColumnRequirements.preferred = r.preferred;
582         totalColumnRequirements.maximum = r.maximum;
583 
584         // set the alignment
585         Object o = attr.getAttribute(CSS.Attribute.TEXT_ALIGN);
586         if (o != null) {
587             // set horizontal alignment
588             String ta = o.toString();
589             if (ta.equals("left")) {
590                 r.alignment = 0;
591             } else if (ta.equals("center")) {
592                 r.alignment = 0.5f;
593             } else if (ta.equals("right")) {
594                 r.alignment = 1;
595             } else {
596                 r.alignment = 0;
597             }
598         } else {
599             r.alignment = 0;
600         }
601 
602         return r;
603     }
604 
605     /**
606      * Calculate the requirements for the major axis.  This is called by
607      * the superclass whenever the requirements need to be updated (i.e.
608      * a preferenceChanged was messaged through this view).
609      * <p>
610      * This is implemented to provide the superclass behavior adjusted for
611      * multi-row table cells.
612      */
calculateMajorAxisRequirements(int axis, SizeRequirements r)613     protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
614         updateInsets();
615         rowIterator.updateAdjustments();
616         r = CSS.calculateTiledRequirements(rowIterator, r);
617         r.maximum = r.preferred;
618         return r;
619     }
620 
621     /**
622      * Perform layout for the minor axis of the box (i.e. the
623      * axis orthogonal to the axis that it represents).  The results
624      * of the layout should be placed in the given arrays which represent
625      * the allocations to the children along the minor axis.  This
626      * is called by the superclass whenever the layout needs to be
627      * updated along the minor axis.
628      * <p>
629      * This is implemented to call the
630      * <a href="#layoutColumns">layoutColumns</a> method, and then
631      * forward to the superclass to actually carry out the layout
632      * of the tables rows.
633      *
634      * @param targetSpan the total span given to the view, which
635      *  would be used to layout the children
636      * @param axis the axis being layed out
637      * @param offsets the offsets from the origin of the view for
638      *  each of the child views.  This is a return value and is
639      *  filled in by the implementation of this method
640      * @param spans the span of each child view;  this is a return
641      *  value and is filled in by the implementation of this method
642      * @return the offset and span for each child view in the
643      *  offsets and spans parameters
644      */
layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans)645     protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
646         // make grid is properly represented
647         updateGrid();
648 
649         // all of the row layouts are invalid, so mark them that way
650         int n = getRowCount();
651         for (int i = 0; i < n; i++) {
652             RowView row = getRow(i);
653             row.layoutChanged(axis);
654         }
655 
656         // calculate column spans
657         layoutColumns(targetSpan, columnOffsets, columnSpans, columnRequirements);
658 
659         // continue normal layout
660         super.layoutMinorAxis(targetSpan, axis, offsets, spans);
661     }
662 
663 
664     /**
665      * Perform layout for the major axis of the box (i.e. the
666      * axis that it represents).  The results
667      * of the layout should be placed in the given arrays which represent
668      * the allocations to the children along the minor axis.  This
669      * is called by the superclass whenever the layout needs to be
670      * updated along the minor axis.
671      * <p>
672      * This method is where the layout of the table rows within the
673      * table takes place.  This method is implemented to call the use
674      * the RowIterator and the CSS collapsing tile to layout
675      * with border spacing and border collapsing capabilities.
676      *
677      * @param targetSpan the total span given to the view, which
678      *  would be used to layout the children
679      * @param axis the axis being layed out
680      * @param offsets the offsets from the origin of the view for
681      *  each of the child views; this is a return value and is
682      *  filled in by the implementation of this method
683      * @param spans the span of each child view; this is a return
684      *  value and is filled in by the implementation of this method
685      * @return the offset and span for each child view in the
686      *  offsets and spans parameters
687      */
layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans)688     protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
689         rowIterator.setLayoutArrays(offsets, spans);
690         CSS.calculateTiledLayout(rowIterator, targetSpan);
691 
692         if (captionIndex != -1) {
693             // place the caption
694             View caption = getView(captionIndex);
695             int h = (int) caption.getPreferredSpan(Y_AXIS);
696             spans[captionIndex] = h;
697             short boxBottom = (short) painter.getInset(BOTTOM, this);
698             if (boxBottom != getBottomInset()) {
699                 offsets[captionIndex] = targetSpan + boxBottom;
700             } else {
701                 offsets[captionIndex] = - getTopInset();
702             }
703         }
704     }
705 
706     /**
707      * Fetches the child view that represents the given position in
708      * the model.  This is implemented to walk through the children
709      * looking for a range that contains the given position.  In this
710      * view the children do not necessarily have a one to one mapping
711      * with the child elements.
712      *
713      * @param pos  the search position >= 0
714      * @param a  the allocation to the table on entry, and the
715      *   allocation of the view containing the position on exit
716      * @return  the view representing the given position, or
717      *   null if there isn't one
718      */
getViewAtPosition(int pos, Rectangle a)719     protected View getViewAtPosition(int pos, Rectangle a) {
720         int n = getViewCount();
721         for (int i = 0; i < n; i++) {
722             View v = getView(i);
723             int p0 = v.getStartOffset();
724             int p1 = v.getEndOffset();
725             if ((pos >= p0) && (pos < p1)) {
726                 // it's in this view.
727                 if (a != null) {
728                     childAllocation(i, a);
729                 }
730                 return v;
731             }
732         }
733         if (pos == getEndOffset()) {
734             View v = getView(n - 1);
735             if (a != null) {
736                 this.childAllocation(n - 1, a);
737             }
738             return v;
739         }
740         return null;
741     }
742 
743     // --- View methods ---------------------------------------------
744 
745     /**
746      * Fetches the attributes to use when rendering.  This is
747      * implemented to multiplex the attributes specified in the
748      * model with a StyleSheet.
749      */
getAttributes()750     public AttributeSet getAttributes() {
751         if (attr == null) {
752             StyleSheet sheet = getStyleSheet();
753             attr = sheet.getViewAttributes(this);
754         }
755         return attr;
756     }
757 
758     /**
759      * Renders using the given rendering surface and area on that
760      * surface.  This is implemented to delegate to the css box
761      * painter to paint the border and background prior to the
762      * interior.  The superclass culls rendering the children
763      * that don't directly intersect the clip and the row may
764      * have cells hanging from a row above in it.  The table
765      * does not use the superclass rendering behavior and instead
766      * paints all of the rows and lets the rows cull those
767      * cells not intersecting the clip region.
768      *
769      * @param g the rendering surface to use
770      * @param allocation the allocated region to render into
771      * @see View#paint
772      */
paint(Graphics g, Shape allocation)773     public void paint(Graphics g, Shape allocation) {
774         // paint the border
775         Rectangle a = allocation.getBounds();
776         setSize(a.width, a.height);
777         if (captionIndex != -1) {
778             // adjust the border for the caption
779             short top = (short) painter.getInset(TOP, this);
780             short bottom = (short) painter.getInset(BOTTOM, this);
781             if (top != getTopInset()) {
782                 int h = getTopInset() - top;
783                 a.y += h;
784                 a.height -= h;
785             } else {
786                 a.height -= getBottomInset() - bottom;
787             }
788         }
789         painter.paint(g, a.x, a.y, a.width, a.height, this);
790         // paint interior
791         int n = getViewCount();
792         for (int i = 0; i < n; i++) {
793             View v = getView(i);
794             v.paint(g, getChildAllocation(i, allocation));
795         }
796         //super.paint(g, a);
797     }
798 
799     /**
800      * Establishes the parent view for this view.  This is
801      * guaranteed to be called before any other methods if the
802      * parent view is functioning properly.
803      * <p>
804      * This is implemented
805      * to forward to the superclass as well as call the
806      * <a href="#setPropertiesFromAttributes">setPropertiesFromAttributes</a>
807      * method to set the paragraph properties from the css
808      * attributes.  The call is made at this time to ensure
809      * the ability to resolve upward through the parents
810      * view attributes.
811      *
812      * @param parent the new parent, or null if the view is
813      *  being removed from a parent it was previously added
814      *  to
815      */
setParent(View parent)816     public void setParent(View parent) {
817         super.setParent(parent);
818         if (parent != null) {
819             setPropertiesFromAttributes();
820         }
821     }
822 
823     /**
824      * Fetches the ViewFactory implementation that is feeding
825      * the view hierarchy.
826      * This replaces the ViewFactory with an implementation that
827      * calls through to the createTableRow and createTableCell
828      * methods.   If the element given to the factory isn't a
829      * table row or cell, the request is delegated to the factory
830      * produced by the superclass behavior.
831      *
832      * @return the factory, null if none
833      */
getViewFactory()834     public ViewFactory getViewFactory() {
835         return this;
836     }
837 
838     /**
839      * Gives notification that something was inserted into
840      * the document in a location that this view is responsible for.
841      * This replaces the ViewFactory with an implementation that
842      * calls through to the createTableRow and createTableCell
843      * methods.   If the element given to the factory isn't a
844      * table row or cell, the request is delegated to the factory
845      * passed as an argument.
846      *
847      * @param e the change information from the associated document
848      * @param a the current allocation of the view
849      * @param f the factory to use to rebuild if the view has children
850      * @see View#insertUpdate
851      */
insertUpdate(DocumentEvent e, Shape a, ViewFactory f)852     public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
853         super.insertUpdate(e, a, this);
854     }
855 
856     /**
857      * Gives notification that something was removed from the document
858      * in a location that this view is responsible for.
859      * This replaces the ViewFactory with an implementation that
860      * calls through to the createTableRow and createTableCell
861      * methods.   If the element given to the factory isn't a
862      * table row or cell, the request is delegated to the factory
863      * passed as an argument.
864      *
865      * @param e the change information from the associated document
866      * @param a the current allocation of the view
867      * @param f the factory to use to rebuild if the view has children
868      * @see View#removeUpdate
869      */
removeUpdate(DocumentEvent e, Shape a, ViewFactory f)870     public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
871         super.removeUpdate(e, a, this);
872     }
873 
874     /**
875      * Gives notification from the document that attributes were changed
876      * in a location that this view is responsible for.
877      * This replaces the ViewFactory with an implementation that
878      * calls through to the createTableRow and createTableCell
879      * methods.   If the element given to the factory isn't a
880      * table row or cell, the request is delegated to the factory
881      * passed as an argument.
882      *
883      * @param e the change information from the associated document
884      * @param a the current allocation of the view
885      * @param f the factory to use to rebuild if the view has children
886      * @see View#changedUpdate
887      */
changedUpdate(DocumentEvent e, Shape a, ViewFactory f)888     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
889         super.changedUpdate(e, a, this);
890     }
891 
forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, Shape a, ViewFactory f)892     protected void forwardUpdate(DocumentEvent.ElementChange ec,
893                                  DocumentEvent e, Shape a, ViewFactory f) {
894         super.forwardUpdate(ec, e, a, f);
895         // A change in any of the table cells usually effects the whole table,
896         // so redraw it all!
897         if (a != null) {
898             Component c = getContainer();
899             if (c != null) {
900                 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
901                                    a.getBounds();
902                 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
903             }
904         }
905     }
906 
907     /**
908      * Change the child views.  This is implemented to
909      * provide the superclass behavior and invalidate the
910      * grid so that rows and columns will be recalculated.
911      */
replace(int offset, int length, View[] views)912     public void replace(int offset, int length, View[] views) {
913         super.replace(offset, length, views);
914         invalidateGrid();
915     }
916 
917     // --- ViewFactory methods ------------------------------------------
918 
919     /**
920      * The table itself acts as a factory for the various
921      * views that actually represent pieces of the table.
922      * All other factory activity is delegated to the factory
923      * returned by the parent of the table.
924      */
create(Element elem)925     public View create(Element elem) {
926         Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
927         if (o instanceof HTML.Tag) {
928             HTML.Tag kind = (HTML.Tag) o;
929             if (kind == HTML.Tag.TR) {
930                 return createTableRow(elem);
931             } else if ((kind == HTML.Tag.TD) || (kind == HTML.Tag.TH)) {
932                 return new CellView(elem);
933             } else if (kind == HTML.Tag.CAPTION) {
934                 return new javax.swing.text.html.ParagraphView(elem);
935             }
936         }
937         // default is to delegate to the normal factory
938         View p = getParent();
939         if (p != null) {
940             ViewFactory f = p.getViewFactory();
941             if (f != null) {
942                 return f.create(elem);
943             }
944         }
945         return null;
946     }
947 
948     // ---- variables ----------------------------------------------------
949 
950     private AttributeSet attr;
951     private StyleSheet.BoxPainter painter;
952 
953     private int cellSpacing;
954     private int borderWidth;
955 
956     /**
957      * The index of the caption view if there is a caption.
958      * This has a value of -1 if there is no caption.  The
959      * caption lives in the inset area of the table, and is
960      * updated with each time the grid is recalculated.
961      */
962     private int captionIndex;
963 
964     /**
965      * Do any of the table cells contain a relative size
966      * specification?  This is updated with each call to
967      * updateGrid().  If this is true, the ColumnIterator
968      * will do extra work to calculate relative cell
969      * specifications.
970      */
971     private boolean relativeCells;
972 
973     /**
974      * Do any of the table cells span multiple rows?  If
975      * true, the RowRequirementIterator will do additional
976      * work to adjust the requirements of rows spanned by
977      * a single table cell.  This is updated with each call to
978      * updateGrid().
979      */
980     private boolean multiRowCells;
981 
982     int[] columnSpans;
983     int[] columnOffsets;
984     /**
985      * SizeRequirements for all the columns.
986      */
987     SizeRequirements totalColumnRequirements;
988     SizeRequirements[] columnRequirements;
989 
990     RowIterator rowIterator = new RowIterator();
991     ColumnIterator colIterator = new ColumnIterator();
992 
993     Vector<RowView> rows;
994 
995     // whether to display comments inside table or not.
996     boolean skipComments = false;
997 
998     boolean gridValid;
999     static final private BitSet EMPTY = new BitSet();
1000 
1001     class ColumnIterator implements CSS.LayoutIterator {
1002 
1003         /**
1004          * Disable percentage adjustments which should only apply
1005          * when calculating layout, not requirements.
1006          */
disablePercentages()1007         void disablePercentages() {
1008             percentages = null;
1009         }
1010 
1011         /**
1012          * Update percentage adjustments if they are needed.
1013          */
updatePercentagesAndAdjustmentWeights(int span)1014         private void updatePercentagesAndAdjustmentWeights(int span) {
1015             adjustmentWeights = new int[columnRequirements.length];
1016             for (int i = 0; i < columnRequirements.length; i++) {
1017                 adjustmentWeights[i] = 0;
1018             }
1019             if (relativeCells) {
1020                 percentages = new int[columnRequirements.length];
1021             } else {
1022                 percentages = null;
1023             }
1024             int nrows = getRowCount();
1025             for (int rowIndex = 0; rowIndex < nrows; rowIndex++) {
1026                 RowView row = getRow(rowIndex);
1027                 int col = 0;
1028                 int ncells = row.getViewCount();
1029                 for (int cell = 0; cell < ncells; cell++, col++) {
1030                     View cv = row.getView(cell);
1031                     for (; row.isFilled(col); col++); // advance to a free column
1032                     int rowSpan = getRowsOccupied(cv);
1033                     int colSpan = getColumnsOccupied(cv);
1034                     AttributeSet a = cv.getAttributes();
1035                     CSS.LengthValue lv = (CSS.LengthValue)
1036                         a.getAttribute(CSS.Attribute.WIDTH);
1037                     if ( lv != null ) {
1038                         int len = (int) (lv.getValue(span) / colSpan + 0.5f);
1039                         for (int i = 0; i < colSpan; i++) {
1040                             if (lv.isPercentage()) {
1041                                 // add a percentage requirement
1042                                 percentages[col+i] = Math.max(percentages[col+i], len);
1043                                 adjustmentWeights[col + i] = Math.max(adjustmentWeights[col + i], WorstAdjustmentWeight);
1044                             } else {
1045                                 adjustmentWeights[col + i] = Math.max(adjustmentWeights[col + i], WorstAdjustmentWeight - 1);
1046                             }
1047                         }
1048                     }
1049                     col += colSpan - 1;
1050                 }
1051             }
1052         }
1053 
1054         /**
1055          * Set the layout arrays to use for holding layout results
1056          */
setLayoutArrays(int offsets[], int spans[], int targetSpan)1057         public void setLayoutArrays(int offsets[], int spans[], int targetSpan) {
1058             this.offsets = offsets;
1059             this.spans = spans;
1060             updatePercentagesAndAdjustmentWeights(targetSpan);
1061         }
1062 
1063         // --- RequirementIterator methods -------------------
1064 
getCount()1065         public int getCount() {
1066             return columnRequirements.length;
1067         }
1068 
setIndex(int i)1069         public void setIndex(int i) {
1070             col = i;
1071         }
1072 
setOffset(int offs)1073         public void setOffset(int offs) {
1074             offsets[col] = offs;
1075         }
1076 
getOffset()1077         public int getOffset() {
1078             return offsets[col];
1079         }
1080 
setSpan(int span)1081         public void setSpan(int span) {
1082             spans[col] = span;
1083         }
1084 
getSpan()1085         public int getSpan() {
1086             return spans[col];
1087         }
1088 
getMinimumSpan(float parentSpan)1089         public float getMinimumSpan(float parentSpan) {
1090             // do not care for percentages, since min span can't
1091             // be less than columnRequirements[col].minimum,
1092             // but can be less than percentage value.
1093             return columnRequirements[col].minimum;
1094         }
1095 
getPreferredSpan(float parentSpan)1096         public float getPreferredSpan(float parentSpan) {
1097             if ((percentages != null) && (percentages[col] != 0)) {
1098                 return Math.max(percentages[col], columnRequirements[col].minimum);
1099             }
1100             return columnRequirements[col].preferred;
1101         }
1102 
getMaximumSpan(float parentSpan)1103         public float getMaximumSpan(float parentSpan) {
1104             return columnRequirements[col].maximum;
1105         }
1106 
getBorderWidth()1107         public float getBorderWidth() {
1108             return borderWidth;
1109         }
1110 
1111 
getLeadingCollapseSpan()1112         public float getLeadingCollapseSpan() {
1113             return cellSpacing;
1114         }
1115 
getTrailingCollapseSpan()1116         public float getTrailingCollapseSpan() {
1117             return cellSpacing;
1118         }
1119 
getAdjustmentWeight()1120         public int getAdjustmentWeight() {
1121             return adjustmentWeights[col];
1122         }
1123 
1124         /**
1125          * Current column index
1126          */
1127         private int col;
1128 
1129         /**
1130          * percentage values (may be null since there
1131          * might not be any).
1132          */
1133         private int[] percentages;
1134 
1135         private int[] adjustmentWeights;
1136 
1137         private int[] offsets;
1138         private int[] spans;
1139     }
1140 
1141     class RowIterator implements CSS.LayoutIterator {
1142 
RowIterator()1143         RowIterator() {
1144         }
1145 
updateAdjustments()1146         void updateAdjustments() {
1147             int axis = Y_AXIS;
1148             if (multiRowCells) {
1149                 // adjust requirements of multi-row cells
1150                 int n = getRowCount();
1151                 adjustments = new int[n];
1152                 for (int i = 0; i < n; i++) {
1153                     RowView rv = getRow(i);
1154                     if (rv.multiRowCells == true) {
1155                         int ncells = rv.getViewCount();
1156                         for (int j = 0; j < ncells; j++) {
1157                             View v = rv.getView(j);
1158                             int nrows = getRowsOccupied(v);
1159                             if (nrows > 1) {
1160                                 int spanNeeded = (int) v.getPreferredSpan(axis);
1161                                 adjustMultiRowSpan(spanNeeded, nrows, i);
1162                             }
1163                         }
1164                     }
1165                 }
1166             } else {
1167                 adjustments = null;
1168             }
1169         }
1170 
1171         /**
1172          * Fixup preferences to accommodate a multi-row table cell
1173          * if not already covered by existing preferences.  This is
1174          * a no-op if not all of the rows needed (to do this check/fixup)
1175          * have arrived yet.
1176          */
adjustMultiRowSpan(int spanNeeded, int nrows, int rowIndex)1177         void adjustMultiRowSpan(int spanNeeded, int nrows, int rowIndex) {
1178             if ((rowIndex + nrows) > getCount()) {
1179                 // rows are missing (could be a bad rowspan specification)
1180                 // or not all the rows have arrived.  Do the best we can with
1181                 // the current set of rows.
1182                 nrows = getCount() - rowIndex;
1183                 if (nrows < 1) {
1184                     return;
1185                 }
1186             }
1187             int span = 0;
1188             for (int i = 0; i < nrows; i++) {
1189                 RowView rv = getRow(rowIndex + i);
1190                 span += rv.getPreferredSpan(Y_AXIS);
1191             }
1192             if (spanNeeded > span) {
1193                 int adjust = (spanNeeded - span);
1194                 int rowAdjust = adjust / nrows;
1195                 int firstAdjust = rowAdjust + (adjust - (rowAdjust * nrows));
1196                 RowView rv = getRow(rowIndex);
1197                 adjustments[rowIndex] = Math.max(adjustments[rowIndex],
1198                                                  firstAdjust);
1199                 for (int i = 1; i < nrows; i++) {
1200                     adjustments[rowIndex + i] = Math.max(
1201                         adjustments[rowIndex + i], rowAdjust);
1202                 }
1203             }
1204         }
1205 
setLayoutArrays(int[] offsets, int[] spans)1206         void setLayoutArrays(int[] offsets, int[] spans) {
1207             this.offsets = offsets;
1208             this.spans = spans;
1209         }
1210 
1211         // --- RequirementIterator methods -------------------
1212 
setOffset(int offs)1213         public void setOffset(int offs) {
1214             RowView rv = getRow(row);
1215             if (rv != null) {
1216                 offsets[rv.viewIndex] = offs;
1217             }
1218         }
1219 
getOffset()1220         public int getOffset() {
1221             RowView rv = getRow(row);
1222             if (rv != null) {
1223                 return offsets[rv.viewIndex];
1224             }
1225             return 0;
1226         }
1227 
setSpan(int span)1228         public void setSpan(int span) {
1229             RowView rv = getRow(row);
1230             if (rv != null) {
1231                 spans[rv.viewIndex] = span;
1232             }
1233         }
1234 
getSpan()1235         public int getSpan() {
1236             RowView rv = getRow(row);
1237             if (rv != null) {
1238                 return spans[rv.viewIndex];
1239             }
1240             return 0;
1241         }
1242 
getCount()1243         public int getCount() {
1244             return rows.size();
1245         }
1246 
setIndex(int i)1247         public void setIndex(int i) {
1248             row = i;
1249         }
1250 
getMinimumSpan(float parentSpan)1251         public float getMinimumSpan(float parentSpan) {
1252             return getPreferredSpan(parentSpan);
1253         }
1254 
getPreferredSpan(float parentSpan)1255         public float getPreferredSpan(float parentSpan) {
1256             RowView rv = getRow(row);
1257             if (rv != null) {
1258                 int adjust = (adjustments != null) ? adjustments[row] : 0;
1259                 return rv.getPreferredSpan(TableView.this.getAxis()) + adjust;
1260             }
1261             return 0;
1262         }
1263 
getMaximumSpan(float parentSpan)1264         public float getMaximumSpan(float parentSpan) {
1265             return getPreferredSpan(parentSpan);
1266         }
1267 
getBorderWidth()1268         public float getBorderWidth() {
1269             return borderWidth;
1270         }
1271 
getLeadingCollapseSpan()1272         public float getLeadingCollapseSpan() {
1273             return cellSpacing;
1274         }
1275 
getTrailingCollapseSpan()1276         public float getTrailingCollapseSpan() {
1277             return cellSpacing;
1278         }
1279 
getAdjustmentWeight()1280         public int getAdjustmentWeight() {
1281             return 0;
1282         }
1283 
1284         /**
1285          * Current row index
1286          */
1287         private int row;
1288 
1289         /**
1290          * Adjustments to the row requirements to handle multi-row
1291          * table cells.
1292          */
1293         private int[] adjustments;
1294 
1295         private int[] offsets;
1296         private int[] spans;
1297     }
1298 
1299     /**
1300      * View of a row in a row-centric table.
1301      */
1302     public class RowView extends BoxView {
1303 
1304         /**
1305          * Constructs a TableView for the given element.
1306          *
1307          * @param elem the element that this view is responsible for
1308          */
RowView(Element elem)1309         public RowView(Element elem) {
1310             super(elem, View.X_AXIS);
1311             fillColumns = new BitSet();
1312             RowView.this.setPropertiesFromAttributes();
1313         }
1314 
clearFilledColumns()1315         void clearFilledColumns() {
1316             fillColumns.and(EMPTY);
1317         }
1318 
fillColumn(int col)1319         void fillColumn(int col) {
1320             fillColumns.set(col);
1321         }
1322 
isFilled(int col)1323         boolean isFilled(int col) {
1324             return fillColumns.get(col);
1325         }
1326 
1327         /**
1328          * The number of columns present in this row.
1329          */
getColumnCount()1330         int getColumnCount() {
1331             int nfill = 0;
1332             int n = fillColumns.size();
1333             for (int i = 0; i < n; i++) {
1334                 if (fillColumns.get(i)) {
1335                     nfill ++;
1336                 }
1337             }
1338             return getViewCount() + nfill;
1339         }
1340 
1341         /**
1342          * Fetches the attributes to use when rendering.  This is
1343          * implemented to multiplex the attributes specified in the
1344          * model with a StyleSheet.
1345          */
getAttributes()1346         public AttributeSet getAttributes() {
1347             return attr;
1348         }
1349 
findViewAtPoint(int x, int y, Rectangle alloc)1350         View findViewAtPoint(int x, int y, Rectangle alloc) {
1351             int n = getViewCount();
1352             for (int i = 0; i < n; i++) {
1353                 if (getChildAllocation(i, alloc).contains(x, y)) {
1354                     childAllocation(i, alloc);
1355                     return getView(i);
1356                 }
1357             }
1358             return null;
1359         }
1360 
getStyleSheet()1361         protected StyleSheet getStyleSheet() {
1362             HTMLDocument doc = (HTMLDocument) getDocument();
1363             return doc.getStyleSheet();
1364         }
1365 
1366         /**
1367          * This is called by a child to indicate its
1368          * preferred span has changed.  This is implemented to
1369          * execute the superclass behavior and well as try to
1370          * determine if a row with a multi-row cell hangs across
1371          * this row.  If a multi-row cell covers this row it also
1372          * needs to propagate a preferenceChanged so that it will
1373          * recalculate the multi-row cell.
1374          *
1375          * @param child the child view
1376          * @param width true if the width preference should change
1377          * @param height true if the height preference should change
1378          */
preferenceChanged(View child, boolean width, boolean height)1379         public void preferenceChanged(View child, boolean width, boolean height) {
1380             super.preferenceChanged(child, width, height);
1381             if (TableView.this.multiRowCells && height) {
1382                 for (int i = rowIndex  - 1; i >= 0; i--) {
1383                     RowView rv = TableView.this.getRow(i);
1384                     if (rv.multiRowCells) {
1385                         rv.preferenceChanged(null, false, true);
1386                         break;
1387                     }
1388                 }
1389             }
1390         }
1391 
1392         // The major axis requirements for a row are dictated by the column
1393         // requirements. These methods use the value calculated by
1394         // TableView.
calculateMajorAxisRequirements(int axis, SizeRequirements r)1395         protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
1396             SizeRequirements req = new SizeRequirements();
1397             req.minimum = totalColumnRequirements.minimum;
1398             req.maximum = totalColumnRequirements.maximum;
1399             req.preferred = totalColumnRequirements.preferred;
1400             req.alignment = 0f;
1401             return req;
1402         }
1403 
getMinimumSpan(int axis)1404         public float getMinimumSpan(int axis) {
1405             float value;
1406 
1407             if (axis == View.X_AXIS) {
1408                 value = totalColumnRequirements.minimum + getLeftInset() +
1409                         getRightInset();
1410             }
1411             else {
1412                 value = super.getMinimumSpan(axis);
1413             }
1414             return value;
1415         }
1416 
getMaximumSpan(int axis)1417         public float getMaximumSpan(int axis) {
1418             float value;
1419 
1420             if (axis == View.X_AXIS) {
1421                 // We're flexible.
1422                 value = (float)Integer.MAX_VALUE;
1423             }
1424             else {
1425                 value = super.getMaximumSpan(axis);
1426             }
1427             return value;
1428         }
1429 
getPreferredSpan(int axis)1430         public float getPreferredSpan(int axis) {
1431             float value;
1432 
1433             if (axis == View.X_AXIS) {
1434                 value = totalColumnRequirements.preferred + getLeftInset() +
1435                         getRightInset();
1436             }
1437             else {
1438                 value = super.getPreferredSpan(axis);
1439             }
1440             return value;
1441         }
1442 
changedUpdate(DocumentEvent e, Shape a, ViewFactory f)1443         public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
1444             super.changedUpdate(e, a, f);
1445             int pos = e.getOffset();
1446             if (pos <= getStartOffset() && (pos + e.getLength()) >=
1447                 getEndOffset()) {
1448                 RowView.this.setPropertiesFromAttributes();
1449             }
1450         }
1451 
1452         /**
1453          * Renders using the given rendering surface and area on that
1454          * surface.  This is implemented to delegate to the css box
1455          * painter to paint the border and background prior to the
1456          * interior.
1457          *
1458          * @param g the rendering surface to use
1459          * @param allocation the allocated region to render into
1460          * @see View#paint
1461          */
paint(Graphics g, Shape allocation)1462         public void paint(Graphics g, Shape allocation) {
1463             Rectangle a = (Rectangle) allocation;
1464             painter.paint(g, a.x, a.y, a.width, a.height, this);
1465             super.paint(g, a);
1466         }
1467 
1468         /**
1469          * Change the child views.  This is implemented to
1470          * provide the superclass behavior and invalidate the
1471          * grid so that rows and columns will be recalculated.
1472          */
replace(int offset, int length, View[] views)1473         public void replace(int offset, int length, View[] views) {
1474             super.replace(offset, length, views);
1475             invalidateGrid();
1476         }
1477 
1478         /**
1479          * Calculate the height requirements of the table row.  The
1480          * requirements of multi-row cells are not considered for this
1481          * calculation.  The table itself will check and adjust the row
1482          * requirements for all the rows that have multi-row cells spanning
1483          * them.  This method updates the multi-row flag that indicates that
1484          * this row and rows below need additional consideration.
1485          */
calculateMinorAxisRequirements(int axis, SizeRequirements r)1486         protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
1487 //          return super.calculateMinorAxisRequirements(axis, r);
1488             long min = 0;
1489             long pref = 0;
1490             long max = 0;
1491             multiRowCells = false;
1492             int n = getViewCount();
1493             for (int i = 0; i < n; i++) {
1494                 View v = getView(i);
1495                 if (getRowsOccupied(v) > 1) {
1496                     multiRowCells = true;
1497                     max = Math.max((int) v.getMaximumSpan(axis), max);
1498                 } else {
1499                     min = Math.max((int) v.getMinimumSpan(axis), min);
1500                     pref = Math.max((int) v.getPreferredSpan(axis), pref);
1501                     max = Math.max((int) v.getMaximumSpan(axis), max);
1502                 }
1503             }
1504 
1505             if (r == null) {
1506                 r = new SizeRequirements();
1507                 r.alignment = 0.5f;
1508             }
1509             r.preferred = (int) pref;
1510             r.minimum = (int) min;
1511             r.maximum = (int) max;
1512             return r;
1513         }
1514 
1515         /**
1516          * Perform layout for the major axis of the box (i.e. the
1517          * axis that it represents).  The results of the layout should
1518          * be placed in the given arrays which represent the allocations
1519          * to the children along the major axis.
1520          * <p>
1521          * This is re-implemented to give each child the span of the column
1522          * width for the table, and to give cells that span multiple columns
1523          * the multi-column span.
1524          *
1525          * @param targetSpan the total span given to the view, which
1526          *  would be used to layout the children
1527          * @param axis the axis being layed out
1528          * @param offsets the offsets from the origin of the view for
1529          *  each of the child views; this is a return value and is
1530          *  filled in by the implementation of this method
1531          * @param spans the span of each child view; this is a return
1532          *  value and is filled in by the implementation of this method
1533          * @return the offset and span for each child view in the
1534          *  offsets and spans parameters
1535          */
layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans)1536         protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
1537             int col = 0;
1538             int ncells = getViewCount();
1539             for (int cell = 0; cell < ncells; cell++) {
1540                 View cv = getView(cell);
1541                 if (skipComments && !(cv instanceof CellView)) {
1542                     continue;
1543                 }
1544                 for (; isFilled(col); col++); // advance to a free column
1545                 int colSpan = getColumnsOccupied(cv);
1546                 spans[cell] = columnSpans[col];
1547                 offsets[cell] = columnOffsets[col];
1548                 if (colSpan > 1) {
1549                     int n = columnSpans.length;
1550                     for (int j = 1; j < colSpan; j++) {
1551                         // Because the table may be only partially formed, some
1552                         // of the columns may not yet exist.  Therefore we check
1553                         // the bounds.
1554                         if ((col+j) < n) {
1555                             spans[cell] += columnSpans[col+j];
1556                             spans[cell] += cellSpacing;
1557                         }
1558                     }
1559                     col += colSpan - 1;
1560                 }
1561                 col++;
1562             }
1563         }
1564 
1565         /**
1566          * Perform layout for the minor axis of the box (i.e. the
1567          * axis orthogonal to the axis that it represents).  The results
1568          * of the layout should be placed in the given arrays which represent
1569          * the allocations to the children along the minor axis.  This
1570          * is called by the superclass whenever the layout needs to be
1571          * updated along the minor axis.
1572          * <p>
1573          * This is implemented to delegate to the superclass, then adjust
1574          * the span for any cell that spans multiple rows.
1575          *
1576          * @param targetSpan the total span given to the view, which
1577          *  would be used to layout the children
1578          * @param axis the axis being layed out
1579          * @param offsets the offsets from the origin of the view for
1580          *  each of the child views; this is a return value and is
1581          *  filled in by the implementation of this method
1582          * @param spans the span of each child view; this is a return
1583          *  value and is filled in by the implementation of this method
1584          * @return the offset and span for each child view in the
1585          *  offsets and spans parameters
1586          */
layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans)1587         protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
1588             super.layoutMinorAxis(targetSpan, axis, offsets, spans);
1589             int col = 0;
1590             int ncells = getViewCount();
1591             for (int cell = 0; cell < ncells; cell++, col++) {
1592                 View cv = getView(cell);
1593                 for (; isFilled(col); col++); // advance to a free column
1594                 int colSpan = getColumnsOccupied(cv);
1595                 int rowSpan = getRowsOccupied(cv);
1596                 if (rowSpan > 1) {
1597 
1598                     int row0 = rowIndex;
1599                     int row1 = Math.min(rowIndex + rowSpan - 1, getRowCount()-1);
1600                     spans[cell] = getMultiRowSpan(row0, row1);
1601                 }
1602                 if (colSpan > 1) {
1603                     col += colSpan - 1;
1604                 }
1605             }
1606         }
1607 
1608         /**
1609          * Determines the resizability of the view along the
1610          * given axis.  A value of 0 or less is not resizable.
1611          *
1612          * @param axis may be either View.X_AXIS or View.Y_AXIS
1613          * @return the resize weight
1614          * @exception IllegalArgumentException for an invalid axis
1615          */
getResizeWeight(int axis)1616         public int getResizeWeight(int axis) {
1617             return 1;
1618         }
1619 
1620         /**
1621          * Fetches the child view that represents the given position in
1622          * the model.  This is implemented to walk through the children
1623          * looking for a range that contains the given position.  In this
1624          * view the children do not necessarily have a one to one mapping
1625          * with the child elements.
1626          *
1627          * @param pos  the search position >= 0
1628          * @param a  the allocation to the table on entry, and the
1629          *   allocation of the view containing the position on exit
1630          * @return  the view representing the given position, or
1631          *   null if there isn't one
1632          */
getViewAtPosition(int pos, Rectangle a)1633         protected View getViewAtPosition(int pos, Rectangle a) {
1634             int n = getViewCount();
1635             for (int i = 0; i < n; i++) {
1636                 View v = getView(i);
1637                 int p0 = v.getStartOffset();
1638                 int p1 = v.getEndOffset();
1639                 if ((pos >= p0) && (pos < p1)) {
1640                     // it's in this view.
1641                     if (a != null) {
1642                         childAllocation(i, a);
1643                     }
1644                     return v;
1645                 }
1646             }
1647             if (pos == getEndOffset()) {
1648                 View v = getView(n - 1);
1649                 if (a != null) {
1650                     this.childAllocation(n - 1, a);
1651                 }
1652                 return v;
1653             }
1654             return null;
1655         }
1656 
1657         /**
1658          * Update any cached values that come from attributes.
1659          */
setPropertiesFromAttributes()1660         void setPropertiesFromAttributes() {
1661             StyleSheet sheet = getStyleSheet();
1662             attr = sheet.getViewAttributes(this);
1663             painter = sheet.getBoxPainter(attr);
1664         }
1665 
1666         private StyleSheet.BoxPainter painter;
1667         private AttributeSet attr;
1668 
1669         /** columns filled by multi-column or multi-row cells */
1670         BitSet fillColumns;
1671 
1672         /**
1673          * The row index within the overall grid
1674          */
1675         int rowIndex;
1676 
1677         /**
1678          * The view index (for row index to view index conversion).
1679          * This is set by the updateGrid method.
1680          */
1681         int viewIndex;
1682 
1683         /**
1684          * Does this table row have cells that span multiple rows?
1685          */
1686         boolean multiRowCells;
1687 
1688     }
1689 
1690     /**
1691      * Default view of an html table cell.  This needs to be moved
1692      * somewhere else.
1693      */
1694     class CellView extends BlockView {
1695 
1696         /**
1697          * Constructs a TableCell for the given element.
1698          *
1699          * @param elem the element that this view is responsible for
1700          */
CellView(Element elem)1701         public CellView(Element elem) {
1702             super(elem, Y_AXIS);
1703         }
1704 
1705         /**
1706          * Perform layout for the major axis of the box (i.e. the
1707          * axis that it represents).  The results of the layout should
1708          * be placed in the given arrays which represent the allocations
1709          * to the children along the major axis.  This is called by the
1710          * superclass to recalculate the positions of the child views
1711          * when the layout might have changed.
1712          * <p>
1713          * This is implemented to delegate to the superclass to
1714          * tile the children.  If the target span is greater than
1715          * was needed, the offsets are adjusted to align the children
1716          * (i.e. position according to the html valign attribute).
1717          *
1718          * @param targetSpan the total span given to the view, which
1719          *  would be used to layout the children
1720          * @param axis the axis being layed out
1721          * @param offsets the offsets from the origin of the view for
1722          *  each of the child views; this is a return value and is
1723          *  filled in by the implementation of this method
1724          * @param spans the span of each child view; this is a return
1725          *  value and is filled in by the implementation of this method
1726          * @return the offset and span for each child view in the
1727          *  offsets and spans parameters
1728          */
layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans)1729         protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
1730             super.layoutMajorAxis(targetSpan, axis, offsets, spans);
1731             // calculate usage
1732             int used = 0;
1733             int n = spans.length;
1734             for (int i = 0; i < n; i++) {
1735                 used += spans[i];
1736             }
1737 
1738             // calculate adjustments
1739             int adjust = 0;
1740             if (used < targetSpan) {
1741                 // PENDING(prinz) change to use the css alignment.
1742                 String valign = (String) getElement().getAttributes().getAttribute(
1743                     HTML.Attribute.VALIGN);
1744                 if (valign == null) {
1745                     AttributeSet rowAttr = getElement().getParentElement().getAttributes();
1746                     valign = (String) rowAttr.getAttribute(HTML.Attribute.VALIGN);
1747                 }
1748                 if ((valign == null) || valign.equals("middle")) {
1749                     adjust = (targetSpan - used) / 2;
1750                 } else if (valign.equals("bottom")) {
1751                     adjust = targetSpan - used;
1752                 }
1753             }
1754 
1755             // make adjustments.
1756             if (adjust != 0) {
1757                 for (int i = 0; i < n; i++) {
1758                     offsets[i] += adjust;
1759                 }
1760             }
1761         }
1762 
1763         /**
1764          * Calculate the requirements needed along the major axis.
1765          * This is called by the superclass whenever the requirements
1766          * need to be updated (i.e. a preferenceChanged was messaged
1767          * through this view).
1768          * <p>
1769          * This is implemented to delegate to the superclass, but
1770          * indicate the maximum size is very large (i.e. the cell
1771          * is willing to expend to occupy the full height of the row).
1772          *
1773          * @param axis the axis being layed out.
1774          * @param r the requirements to fill in.  If null, a new one
1775          *  should be allocated.
1776          */
calculateMajorAxisRequirements(int axis, SizeRequirements r)1777         protected SizeRequirements calculateMajorAxisRequirements(int axis,
1778                                                                   SizeRequirements r) {
1779             SizeRequirements req = super.calculateMajorAxisRequirements(axis, r);
1780             req.maximum = Integer.MAX_VALUE;
1781             return req;
1782         }
1783 
1784         @Override
calculateMinorAxisRequirements(int axis, SizeRequirements r)1785         protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
1786             SizeRequirements rv = super.calculateMinorAxisRequirements(axis, r);
1787             //for the cell the minimum should be derived from the child views
1788             //the parent behaviour is to use CSS for that
1789             int n = getViewCount();
1790             int min = 0;
1791             for (int i = 0; i < n; i++) {
1792                 View v = getView(i);
1793                 min = Math.max((int) v.getMinimumSpan(axis), min);
1794             }
1795             rv.minimum = Math.min(rv.minimum, min);
1796             return rv;
1797         }
1798     }
1799 
1800 
1801 }
1802