/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ package org.lobobrowser.html.renderer; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.lobobrowser.html.HtmlAttributeProperties; import org.lobobrowser.html.HtmlRendererContext; import org.lobobrowser.html.domfilter.CaptionFilter; import org.lobobrowser.html.domfilter.ColumnsFilter; import org.lobobrowser.html.domimpl.DOMNodeImpl; import org.lobobrowser.html.domimpl.HTMLElementImpl; import org.lobobrowser.html.domimpl.HTMLTableCaptionElementImpl; import org.lobobrowser.html.domimpl.HTMLTableCellElementImpl; import org.lobobrowser.html.domimpl.HTMLTableElementImpl; import org.lobobrowser.html.domimpl.HTMLTableRowElementImpl; import org.lobobrowser.html.info.CaptionSizeInfo; import org.lobobrowser.html.info.SizeInfo; import org.lobobrowser.html.renderstate.RenderState; import org.lobobrowser.html.style.AbstractCSS2Properties; import org.lobobrowser.html.style.CSSValuesProperties; import org.lobobrowser.html.style.HtmlLength; import org.lobobrowser.html.style.HtmlValues; import org.lobobrowser.html.style.RenderThreadState; import org.lobobrowser.http.UserAgentContext; import org.lobobrowser.w3c.html.HTMLTableRowElement; /** * The Class TableMatrix. */ public class TableMatrix implements CSSValuesProperties{ /** The Constant logger. */ private static final Logger logger = LogManager.getLogger(TableMatrix.class); /** The rows. */ private final ArrayList<ArrayList<VirtualCell>> ROWS = new ArrayList<ArrayList<VirtualCell>>(); /** The all cells. */ private final ArrayList<BoundableRenderable> ALL_CELLS = new ArrayList<BoundableRenderable>(); /** The row elements. */ private final ArrayList<HTMLTableRowElementImpl> ROW_ELEMENTS = new ArrayList<HTMLTableRowElementImpl>(); /** The caption. */ private RTableCaption caption; /** The caption element. */ private HTMLTableCaptionElementImpl captionElement; /** The table element. */ private final HTMLElementImpl tableElement; /** The parser context. */ private final UserAgentContext parserContext; /** The renderer context. */ private final HtmlRendererContext rendererContext; /** The frame context. */ private final FrameContext frameContext; /** The relement. */ private final RElement relement; /** The container. */ private final RenderableContainer container; /** The column sizes. */ private SizeInfo[] columnSizes; /** The row sizes. */ private SizeInfo[] rowSizes; /** The caption size. */ private CaptionSizeInfo captionSize; /** The table width. */ private int tableWidth; /** The table height. */ private int tableHeight; /** The has old style border. */ private int hasOldStyleBorder; /** The cell spacing y. */ private int cellSpacingY; /** The cell spacing x. */ private int cellSpacingX; /** The widths of extras. */ private int widthsOfExtras; /** The heights of extras. */ private int heightsOfExtras; /** The table width length. */ private HtmlLength tableWidthLength; /** * Instantiates a new table matrix. * * @param element * the element * @param pcontext * the pcontext * @param rcontext * the rcontext * @param frameContext * the frame context * @param tableAsContainer * the table as container * @param relement * the relement */ public TableMatrix(HTMLElementImpl element, UserAgentContext pcontext, HtmlRendererContext rcontext, FrameContext frameContext, RenderableContainer tableAsContainer, RElement relement) { this.tableElement = element; this.parserContext = pcontext; this.rendererContext = rcontext; this.frameContext = frameContext; this.relement = relement; this.container = tableAsContainer; } /* * (non-Javadoc) * * @see java.lang.Object#finalize() */ @Override public void finalize() throws Throwable { super.finalize(); } /** * Gets the num rows. * * @return the num rows */ public int getNumRows() { return this.ROWS.size(); } /** * Gets the num columns. * * @return the num columns */ public int getNumColumns() { return this.columnSizes.length; } /** * Gets the table height. * * @return the table height */ public int getTableHeight() { return this.tableHeight; } /** * Gets the table width. * * @return the table width */ public int getTableWidth() { return this.tableWidth; } /** * Gets the table height without caption. * * @return the table height without caption */ public int getTableHeightWithoutCaption() { if (this.captionSize != null) { return this.tableHeight - this.captionSize.getHeight(); } else { return this.tableHeight; } } /** * Gets the start y without caption. * * @return the start y without caption */ public int getStartYWithoutCaption() { if ((this.captionSize != null) && !isCaptionBotton()) { return this.captionSize.getHeight(); } return 0; } /** * Called on every relayout. Element children might have changed. * * @param insets * the insets * @param availWidth * the avail width * @param availHeight * the avail height */ public void reset(Insets insets, int availWidth, int availHeight) { // TODO: Incorporate into build() and calculate // sizes properly based on parameters. ROWS.clear(); ALL_CELLS.clear(); ROW_ELEMENTS.clear(); String borderText = this.tableElement.getAttribute(HtmlAttributeProperties.BORDER); int border = HtmlValues.getPixelSize(borderText, this.tableElement.getRenderState(), 0); String cellSpacingText = this.tableElement.getAttribute(HtmlAttributeProperties.CELLSPACING); int cellSpacing = HtmlValues.getPixelSize(cellSpacingText, this.tableElement.getRenderState(), 0); this.cellSpacingX = cellSpacing; this.cellSpacingY = cellSpacing; this.tableWidthLength = TableMatrix.getWidthLength(this.tableElement, availWidth); this.populateRows(); this.adjustForCellSpans(); this.createSizeArrays(); // Calculate widths of extras SizeInfo[] columnSizes = this.columnSizes; int numCols = columnSizes.length; int widthsOfExtras = insets.left + insets.right + ((numCols + 1) * cellSpacing); if (border > 0) { widthsOfExtras += (numCols * 2); } this.widthsOfExtras = widthsOfExtras; // Calculate heights of extras SizeInfo[] rowSizes = this.rowSizes; int numRows = rowSizes.length; int heightsOfExtras = insets.top + insets.bottom + ((numRows + 1) * cellSpacing); if (border > 0) { heightsOfExtras += (numRows * 2); } this.heightsOfExtras = heightsOfExtras; this.hasOldStyleBorder = border > 0 ? 1 : 0; } /** * Builds the. * * @param availWidth * the avail width * @param availHeight * the avail height * @param sizeOnly * the size only */ public void build(int availWidth, int availHeight, boolean sizeOnly) { int hasBorder = this.hasOldStyleBorder; this.determineColumnSizes(hasBorder, this.cellSpacingX, this.cellSpacingY, availWidth); this.determineRowSizes(hasBorder, this.cellSpacingY, availHeight, sizeOnly); } /** * Gets the parent row. * * @param cellNode * the cell node * @return the parent row */ private final HTMLTableRowElementImpl getParentRow(HTMLTableCellElementImpl cellNode) { org.w3c.dom.Node parentNode = cellNode.getParentNode(); for (;;) { if (parentNode instanceof HTMLTableRowElementImpl) { return (HTMLTableRowElementImpl) parentNode; } if (parentNode instanceof HTMLTableElementImpl) { return null; } parentNode = parentNode.getParentNode(); } } /** * Gets the width length. * * @param element * the element * @param availWidth * the avail width * @return the width length */ private static HtmlLength getWidthLength(HTMLElementImpl element, int availWidth) { try { AbstractCSS2Properties props = element.getCurrentStyle(); String widthText = props == null ? null : props.getWidth(); if (widthText == null) { String widthAttr = element.getAttribute(HtmlAttributeProperties.WIDTH); if (widthAttr == null) { return null; } return new HtmlLength(widthAttr); } else { if(INHERIT.equals(widthText)){ widthText = element.getParentStyle().getWidth(); } int width = -1; if (widthText !=null){ width = HtmlValues.getPixelSize(widthText, element.getRenderState(), 0, availWidth); } if (props.getMaxWidth() != null) { int maxWidth = HtmlValues.getPixelSize(props.getMaxWidth(), element.getRenderState(), 0, availWidth); if (width == 0 || width > maxWidth) { width = maxWidth; } } if (props.getMinWidth() != null) { int minWidth = HtmlValues.getPixelSize(props.getMinWidth(), element.getRenderState(), 0, availWidth); if (width == 0 || width < minWidth) { width = minWidth; } } return new HtmlLength(width); } } catch (Exception err) { return null; } } /** * Gets the height length. * * @param element * the element * @param availHeight * the avail height * @return the height length */ private static HtmlLength getHeightLength(HTMLElementImpl element, int availHeight) { try { AbstractCSS2Properties props = element.getCurrentStyle(); String heightText = props == null ? null : props.getHeight(); if (heightText == null) { String ha = element.getAttribute(HtmlAttributeProperties.HEIGHT); if (ha == null) { return null; } else { return new HtmlLength(ha); } } else { if(INHERIT.equals(heightText)){ heightText = element.getParentStyle().getHeight(); } int height = -1; if (heightText !=null){ height = HtmlValues.getPixelSize(heightText, element.getRenderState(), 0, availHeight); } if (props.getMaxWidth() != null) { int maxHeight = HtmlValues.getPixelSize(props.getMaxHeight(), element.getRenderState(), 0, availHeight); if (height == 0 || height > maxHeight) { height = maxHeight; } } if (props.getMinHeight() != null) { int minHeight = HtmlValues.getPixelSize(props.getMinHeight(), element.getRenderState(), 0, availHeight); if (height == 0 || height < minHeight) { height = minHeight; } } return new HtmlLength(HtmlValues.getPixelSize(heightText, element.getRenderState(), 0, availHeight)); } } catch (Exception err) { return null; } } /** * Populates the ROWS and ALL_CELLS collections. */ private void populateRows() { HTMLElementImpl te = this.tableElement; ArrayList<ArrayList<VirtualCell>> rows = this.ROWS; ArrayList<HTMLTableRowElementImpl> rowElements = this.ROW_ELEMENTS; ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; Map<HTMLTableRowElementImpl, ArrayList<VirtualCell>> rowElementToRowArray = new HashMap<HTMLTableRowElementImpl, ArrayList<VirtualCell>>(2); ArrayList<DOMNodeImpl> cellList = te.getDescendents( new ColumnsFilter(), false); ArrayList<VirtualCell> currentNullRow = null; Iterator<DOMNodeImpl> ci = cellList.iterator(); while (ci.hasNext()) { HTMLTableCellElementImpl columnNode = (HTMLTableCellElementImpl) ci.next(); HTMLTableRowElementImpl rowElement = this.getParentRow(columnNode); if ((rowElement != null) && (rowElement.getRenderState().getDisplay() == RenderState.DISPLAY_NONE)) { // Skip row [ 2047122 ] continue; } ArrayList<VirtualCell> row; if (rowElement != null) { currentNullRow = null; row = rowElementToRowArray.get(rowElement); if (row == null) { row = new ArrayList<VirtualCell>(); rowElementToRowArray.put(rowElement, row); rows.add(row); rowElements.add(rowElement); } } else { // Doesn't have a TR parent. Let's add a ROW just for itself. // Both IE and FireFox have this behavior. if (currentNullRow != null) { row = currentNullRow; } else { row = new ArrayList<VirtualCell>(); currentNullRow = row; rows.add(row); // Null TR element must be added to match. rowElements.add(null); } } RTableCell ac = (RTableCell) columnNode.getUINode(); if (ac == null) { // Saved UI nodes must be reused, because they // can contain a collection of GUI components. ac = new RTableCell(columnNode, this.parserContext, this.rendererContext, this.frameContext, this.container); ac.setParent(this.relement); columnNode.setUINode(ac); } VirtualCell vc = new VirtualCell(ac, true); ac.setTopLeftVirtualCell(vc); row.add(vc); allCells.add(ac); } ArrayList<DOMNodeImpl> captionList = te.getDescendents(new CaptionFilter(), false); if (captionList.size() > 0) { HTMLTableCaptionElementImpl capt = (HTMLTableCaptionElementImpl) captionList.get(0); this.captionElement = capt; this.caption = new RTableCaption(capt, this.parserContext, this.rendererContext, this.frameContext, this.container); } else { this.caption = null; } } /** * Based on colspans and rowspans, creates additional virtual cells from * actual table cells. */ private void adjustForCellSpans() { ArrayList<ArrayList<VirtualCell>> rows = this.ROWS; int numRows = rows.size(); for (int r = 0; r < numRows; r++) { ArrayList<VirtualCell> row = rows.get(r); int numCols = row.size(); for (int c = 0; c < numCols; c++) { VirtualCell vc = (VirtualCell) row.get(c); if ((vc != null) && vc.isTopLeft()) { RTableCell ac = vc.getActualCell(); int colspan = ac.getColSpan(); if (colspan < 1) { colspan = 1; } int rowspan = ac.getRowSpan(); if (rowspan < 1) { rowspan = 1; } // Can't go beyond last row (Fix bug #2022584) int targetRows = r + rowspan; if (numRows < targetRows) { rowspan = numRows - r; ac.setRowSpan(rowspan); } numRows = rows.size(); for (int y = 0; y < rowspan; y++) { if ((colspan > 1) || (y > 0)) { // Get row int nr = r + y; ArrayList<VirtualCell> newRow = rows.get(nr); // Insert missing cells in row int xstart = y == 0 ? 1 : 0; // Insert virtual cells, potentially // shifting others to the right. for (int cc = xstart; cc < colspan; cc++) { int nc = c + cc; while (newRow.size() < nc) { newRow.add(null); } newRow.add(nc, new VirtualCell(ac, false)); } if (row == newRow) { numCols = row.size(); } } } } } } // Adjust row and column of virtual cells for (int r = 0; r < numRows; r++) { ArrayList<VirtualCell> row = rows.get(r); int numCols = row.size(); for (int c = 0; c < numCols; c++) { VirtualCell vc = (VirtualCell) row.get(c); if (vc != null) { vc.setColumn(c); vc.setRow(r); } } } } /** * Populates the columnSizes and rowSizes arrays, setting htmlLength in each * element. */ private void createSizeArrays() { ArrayList<ArrayList<VirtualCell>> rows = this.ROWS; int numRows = rows.size(); SizeInfo[] rowSizes = new SizeInfo[numRows]; this.rowSizes = rowSizes; int numCols = 0; ArrayList<HTMLTableRowElementImpl> rowElements = this.ROW_ELEMENTS; for (int i = 0; i < numRows; i++) { ArrayList<VirtualCell> row = rows.get(i); int rs = row.size(); if (rs > numCols) { numCols = rs; } SizeInfo rowSizeInfo = new SizeInfo(); rowSizes[i] = rowSizeInfo; HTMLTableRowElement rowElement; try { rowElement = rowElements.get(i); // Possible rowElement is null because TD does not have TR // parent } catch (IndexOutOfBoundsException iob) { // Possible if rowspan expands beyond that rowElement = null; } // TODO: TR.height an IE quirk? String rowHeightText = rowElement == null ? null : rowElement.getAttribute(HtmlAttributeProperties.HEIGHT); HtmlLength rowHeightLength = null; if (rowHeightText != null) { try { rowHeightLength = new HtmlLength(rowHeightText); } catch (Exception err) { // ignore } } if (rowHeightLength != null) { rowSizeInfo.setHtmlLength(rowHeightLength); } else { HtmlLength bestHeightLength = null; for (int x = 0; x < rs; x++) { VirtualCell vc = (VirtualCell) row.get(x); if (vc != null) { HtmlLength vcHeightLength = vc.getHeightLength(); if ((vcHeightLength != null) && vcHeightLength.isPreferredOver(bestHeightLength)) { bestHeightLength = vcHeightLength; } } } rowSizeInfo.setHtmlLength(bestHeightLength); } } SizeInfo[] columnSizes = new SizeInfo[numCols]; this.columnSizes = columnSizes; for (int i = 0; i < numCols; i++) { HtmlLength bestWidthLength = null; // Cells with colspan==1 first. for (int y = 0; y < numRows; y++) { ArrayList<VirtualCell> row = rows.get(y); VirtualCell vc; try { vc = (VirtualCell) row.get(i); } catch (IndexOutOfBoundsException iob) { vc = null; } if (vc != null) { RTableCell ac = vc.getActualCell(); if (ac.getColSpan() == 1) { HtmlLength vcWidthLength = vc.getWidthLength(); if ((vcWidthLength != null) && vcWidthLength.isPreferredOver(bestWidthLength)) { bestWidthLength = vcWidthLength; } } } } // Now cells with colspan>1. if (bestWidthLength == null) { for (int y = 0; y < numRows; y++) { ArrayList<VirtualCell> row = rows.get(y); VirtualCell vc; try { vc = (VirtualCell) row.get(i); } catch (IndexOutOfBoundsException iob) { vc = null; } if (vc != null) { RTableCell ac = vc.getActualCell(); if (ac.getColSpan() > 1) { HtmlLength vcWidthLength = vc.getWidthLength(); if ((vcWidthLength != null) && vcWidthLength.isPreferredOver(bestWidthLength)) { bestWidthLength = vcWidthLength; } } } } } SizeInfo colSizeInfo = new SizeInfo(); colSizeInfo.setHtmlLength(bestWidthLength); columnSizes[i] = colSizeInfo; } if (this.caption != null) { this.captionSize = new CaptionSizeInfo(); } } /** * Determines the size of each column, and the table width. Does the * following: * <ol> * <li>Determine tentative widths. This is done by looking at declared * column widths, any table width, and filling in the blanks. No rendering * is done. The tentative width of columns with no declared width is zero. * * <li>Render all cell blocks. It uses the tentative widths from the * previous step as a desired width. The resulting width is considered a * sort of minimum. If the column width is not defined, use a NOWRAP * override flag to render. * * <li>Check if cell widths are too narrow for the rendered width. In the * case of columns without a declared width, check if they are too wide. * * <li>Finally, adjust widths considering the expected max table size. * Columns are layed out again if necessary to determine if they can really * be shrunk. * </ol> * * @param hasBorder * the has border * @param cellSpacingX * the cell spacing x * @param cellSpacingY * the cell spacing y * @param availWidth * the avail width */ private void determineColumnSizes(int hasBorder, int cellSpacingX, int cellSpacingY, int availWidth) { HtmlLength tableWidthLength = this.tableWidthLength; int tableWidth; boolean widthKnown; if (tableWidthLength != null) { tableWidth = tableWidthLength.getLength(availWidth); widthKnown = true; } else { tableWidth = availWidth; widthKnown = false; } SizeInfo[] columnSizes = this.columnSizes; int widthsOfExtras = this.widthsOfExtras; int cellAvailWidth = tableWidth - widthsOfExtras; if (cellAvailWidth < 0) { // tableWidth += (-cellAvailWidth); cellAvailWidth = 0; } this.determineTentativeSizes(columnSizes, cellAvailWidth); this.preLayout(hasBorder, cellSpacingX, cellSpacingY); // Increases column widths if they are less than minimums of each cell. // por stupid finction maybe actualSize set in previous function - not // using layoutSize this.adjustForRenderWidths(columnSizes); // Adjust for expected total width this.adjustWidthsForExpectedMax(columnSizes, cellAvailWidth, widthKnown, captionSize != null ? captionSize.getWidth() : 0, widthsOfExtras); } /** * This method sets the tentative actual sizes of columns (rows) based on * specified witdhs (heights) if available. * * @param columnSizes * the column sizes * @param cellAvailWidth * the cell avail width */ private void determineTentativeSizes(SizeInfo[] columnSizes, int cellAvailWidth) { int numCols = columnSizes.length; // Look at percentages first int widthUsedByPercent = 0; for (int i = 0; i < numCols; i++) { SizeInfo colSizeInfo = columnSizes[i]; HtmlLength widthLength = colSizeInfo.getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() == HtmlLength.LENGTH)) { int actualSizeInt = widthLength.getLength(cellAvailWidth); widthUsedByPercent += actualSizeInt; colSizeInfo.setActualSize(actualSizeInt); } } // Look at columns with absolute sizes int widthUsedByAbsolute = 0; int numNoWidthColumns = 0; for (int i = 0; i < numCols; i++) { SizeInfo colSizeInfo = columnSizes[i]; HtmlLength widthLength = colSizeInfo.getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() != HtmlLength.LENGTH)) { // TODO: MULTI-LENGTH not supported int actualSizeInt = widthLength.getRawValue(); widthUsedByAbsolute += actualSizeInt; colSizeInfo.setActualSize(actualSizeInt); } else if (widthLength == null) { numNoWidthColumns++; } } // Contract if necessary. This is done again later, but this is // an optimization, as it may prevent re-layout. It is only done // if all columns have some kind of declared width. if (numNoWidthColumns == 0) { int totalWidthUsed = widthUsedByPercent + widthUsedByAbsolute; int difference = totalWidthUsed - cellAvailWidth; // See if absolutes need to be contracted if (difference > 0) { if (widthUsedByAbsolute > 0) { int expectedAbsoluteWidthTotal = widthUsedByAbsolute - difference; if (expectedAbsoluteWidthTotal < 0) { expectedAbsoluteWidthTotal = 0; } double ratio = (double) expectedAbsoluteWidthTotal / widthUsedByAbsolute; for (int i = 0; i < numCols; i++) { SizeInfo sizeInfo = columnSizes[i]; HtmlLength widthLength = columnSizes[i].getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() != HtmlLength.LENGTH)) { int oldActualSize = sizeInfo.getActualSize(); int newActualSize = (int) Math.round(oldActualSize * ratio); sizeInfo.setActualSize(newActualSize); totalWidthUsed += (newActualSize - oldActualSize); } } difference = totalWidthUsed - cellAvailWidth; } // See if percentages need to be contracted if (difference > 0) { if (widthUsedByPercent > 0) { int expectedPercentWidthTotal = widthUsedByPercent - difference; if (expectedPercentWidthTotal < 0) { expectedPercentWidthTotal = 0; } double ratio = (double) expectedPercentWidthTotal / widthUsedByPercent; for (int i = 0; i < numCols; i++) { SizeInfo sizeInfo = columnSizes[i]; HtmlLength widthLength = columnSizes[i].getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() == HtmlLength.LENGTH)) { int oldActualSize = sizeInfo.getActualSize(); int newActualSize = (int) Math.round(oldActualSize * ratio); sizeInfo.setActualSize(newActualSize); totalWidthUsed += (newActualSize - oldActualSize); } } } } } } } /** * Contracts column sizes according to render sizes. * * @param columnSizes * the column sizes */ private void adjustForRenderWidths(SizeInfo[] columnSizes) { int numCols = columnSizes.length; for (int i = 0; i < numCols; i++) { SizeInfo si = columnSizes[i]; if (si.getActualSize() < si.getLayoutSize()) { si.setActualSize(si.getLayoutSize()); } } } /** * Layout column. * * @param columnSizes * the column sizes * @param colSize * the col size * @param col * the col * @param cellSpacingX * the cell spacing x * @param hasBorder * the has border */ private void layoutColumn(SizeInfo[] columnSizes, SizeInfo colSize, int col, int cellSpacingX, int hasBorder) { SizeInfo[] rowSizes = this.rowSizes; ArrayList<ArrayList<VirtualCell>> rows = this.ROWS; int numRows = rows.size(); int actualSize = colSize.getActualSize(); colSize.setLayoutSize(0); for (int row = 0; row < numRows;) { // SizeInfo rowSize = rowSizes[row]; ArrayList<VirtualCell> columns = rows.get(row); VirtualCell vc = null; try { vc = (VirtualCell) columns.get(col); } catch (IndexOutOfBoundsException iob) { vc = null; } RTableCell ac = vc == null ? null : vc.getActualCell(); if (ac != null) { if (ac.getVirtualRow() == row) { // Only process actual cells with a row // beginning at the current row being processed. int colSpan = ac.getColSpan(); if (colSpan > 1) { int firstCol = ac.getVirtualColumn(); int cellExtras = (colSpan - 1) * (cellSpacingX + (2 * hasBorder)); int vcActualWidth = cellExtras; for (int x = 0; x < colSpan; x++) { vcActualWidth += columnSizes[firstCol + x].getActualSize(); } // TODO: better height possible Dimension size = ac.doCellLayout(vcActualWidth, 0, true, true, true); int vcRenderWidth = size.width; int denominator = (vcActualWidth - cellExtras); int newTentativeCellWidth; if (denominator > 0) { newTentativeCellWidth = (actualSize * (vcRenderWidth - cellExtras)) / denominator; } else { newTentativeCellWidth = (vcRenderWidth - cellExtras) / colSpan; } if (newTentativeCellWidth > colSize.getLayoutSize()) { colSize.setLayoutSize(newTentativeCellWidth); } int rowSpan = ac.getRowSpan(); int vch = (size.height - ((rowSpan - 1) * (this.cellSpacingY + (2 * hasBorder)))) / rowSpan; for (int y = 0; y < rowSpan; y++) { if (rowSizes[row + y].getMinSize() < vch) { rowSizes[row + y].setMinSize(vch); } } } else { // TODO: better height possible Dimension size = ac.doCellLayout(actualSize, 0, true, true, true); if (size.width > colSize.getLayoutSize()) { colSize.setLayoutSize(size.width); } int rowSpan = ac.getRowSpan(); int vch = (size.height - ((rowSpan - 1) * (this.cellSpacingY + (2 * hasBorder)))) / rowSpan; for (int y = 0; y < rowSpan; y++) { if (rowSizes[row + y].getMinSize() < vch) { rowSizes[row + y].setMinSize(vch); } } } } } // row = (ac == null ? row + 1 : ac.getVirtualRow() + // ac.getRowSpan()); row++; } } /** * Adjust widths for expected max. * * @param columnSizes * the column sizes * @param cellAvailWidth * the cell avail width * @param expand * the expand * @param captionWith * the caption with * @param widthOfExtras * the width of extras * @return the int */ private int adjustWidthsForExpectedMax(SizeInfo[] columnSizes, int cellAvailWidth, boolean expand, int captionWith, int widthOfExtras) { int hasBorder = this.hasOldStyleBorder; int cellSpacingX = this.cellSpacingX; int currentTotal = 0; int numCols = columnSizes.length; for (int i = 0; i < numCols; i++) { currentTotal += columnSizes[i].getActualSize(); } int difference = currentTotal - cellAvailWidth; if ((difference > 0) || ((difference < 0) && expand)) { // First, try to contract/expand columns with no width currentTotal = expandColumns(columnSizes, cellAvailWidth, expand, hasBorder, cellSpacingX, currentTotal, numCols, difference); } if (this.captionSize != null) { if ((cellAvailWidth + widthOfExtras) > captionWith) { int differenceCaption = currentTotal - captionWith - widthOfExtras; if (differenceCaption < 0) { currentTotal = expandColumns(columnSizes, captionWith, expand, hasBorder, cellSpacingX, currentTotal, numCols, differenceCaption); } this.captionSize.setWidth(currentTotal + widthOfExtras); } else { if ((currentTotal + widthOfExtras) > captionWith) { this.captionSize.setWidth(currentTotal + widthOfExtras); } else { currentTotal = expandColumns(columnSizes, captionWith - widthOfExtras, expand, hasBorder, cellSpacingX, currentTotal, numCols, currentTotal); } } } return currentTotal; } /** * Expand columns. * * @param columnSizes * the column sizes * @param cellAvailWidth * the cell avail width * @param expand * the expand * @param hasBorder * the has border * @param cellSpacingX * the cell spacing x * @param currentTotal * the current total * @param numCols * the num cols * @param difference * the difference * @return the int */ private int expandColumns(SizeInfo[] columnSizes, int cellAvailWidth, boolean expand, int hasBorder, int cellSpacingX, int currentTotal, int numCols, int difference) { int noWidthTotal = 0; int numNoWidth = 0; for (int i = 0; i < numCols; i++) { if (columnSizes[i].getHtmlLength() == null) { numNoWidth++; noWidthTotal += columnSizes[i].getActualSize(); } } if (noWidthTotal > 0) { // TODO: This is not shrinking correctly. int expectedNoWidthTotal = noWidthTotal - difference; if (expectedNoWidthTotal < 0) { expectedNoWidthTotal = 0; } double ratio = (double) expectedNoWidthTotal / noWidthTotal; int noWidthCount = 0; for (int i = 0; i < numCols; i++) { SizeInfo sizeInfo = columnSizes[i]; if (sizeInfo.getHtmlLength() == null) { int oldActualSize = sizeInfo.getActualSize(); int newActualSize; if (++noWidthCount == numNoWidth) { // Last column without a width. int currentDiff = currentTotal - cellAvailWidth; newActualSize = oldActualSize - currentDiff; if (newActualSize < 0) { newActualSize = 0; } } else { newActualSize = (int) Math.round(oldActualSize * ratio); } sizeInfo.setActualSize(newActualSize); if (newActualSize < sizeInfo.getLayoutSize()) { // See if it actually fits. this.layoutColumn(columnSizes, sizeInfo, i, cellSpacingX, hasBorder); if (newActualSize < sizeInfo.getLayoutSize()) { // Didn't fit. newActualSize = sizeInfo.getLayoutSize(); sizeInfo.setActualSize(newActualSize); } } currentTotal += (newActualSize - oldActualSize); } } difference = currentTotal - cellAvailWidth; } // See if absolutes need to be contracted if ((difference > 0) || ((difference < 0) && expand)) { int absoluteWidthTotal = 0; for (int i = 0; i < numCols; i++) { HtmlLength widthLength = columnSizes[i].getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() != HtmlLength.LENGTH)) { absoluteWidthTotal += columnSizes[i].getActualSize(); } } if (absoluteWidthTotal > 0) { int expectedAbsoluteWidthTotal = absoluteWidthTotal - difference; if (expectedAbsoluteWidthTotal < 0) { expectedAbsoluteWidthTotal = 0; } double ratio = (double) expectedAbsoluteWidthTotal / absoluteWidthTotal; for (int i = 0; i < numCols; i++) { SizeInfo sizeInfo = columnSizes[i]; HtmlLength widthLength = columnSizes[i].getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() != HtmlLength.LENGTH)) { int oldActualSize = sizeInfo.getActualSize(); int newActualSize = (int) Math.round(oldActualSize * ratio); sizeInfo.setActualSize(newActualSize); if (newActualSize < sizeInfo.getLayoutSize()) { // See if it actually fits. this.layoutColumn(columnSizes, sizeInfo, i, cellSpacingX, hasBorder); if (newActualSize < sizeInfo.getLayoutSize()) { // Didn't fit. newActualSize = sizeInfo.getLayoutSize(); sizeInfo.setActualSize(newActualSize); } } currentTotal += (newActualSize - oldActualSize); } } difference = currentTotal - cellAvailWidth; } // See if percentages need to be contracted if ((difference > 0) || ((difference < 0) && expand)) { int percentWidthTotal = 0; for (int i = 0; i < numCols; i++) { HtmlLength widthLength = columnSizes[i].getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() == HtmlLength.LENGTH)) { percentWidthTotal += columnSizes[i].getActualSize(); } } if (percentWidthTotal > 0) { int expectedPercentWidthTotal = percentWidthTotal - difference; if (expectedPercentWidthTotal < 0) { expectedPercentWidthTotal = 0; } double ratio = (double) expectedPercentWidthTotal / percentWidthTotal; for (int i = 0; i < numCols; i++) { SizeInfo sizeInfo = columnSizes[i]; HtmlLength widthLength = columnSizes[i].getHtmlLength(); if ((widthLength != null) && (widthLength.getLengthType() == HtmlLength.LENGTH)) { int oldActualSize = sizeInfo.getActualSize(); int newActualSize = (int) Math.round(oldActualSize * ratio); sizeInfo.setActualSize(newActualSize); if (newActualSize < sizeInfo.getLayoutSize()) { // See if it actually fits. this.layoutColumn(columnSizes, sizeInfo, i, cellSpacingX, hasBorder); if (newActualSize < sizeInfo.getLayoutSize()) { // Didn't fit. newActualSize = sizeInfo.getLayoutSize(); sizeInfo.setActualSize(newActualSize); } } currentTotal += (newActualSize - oldActualSize); } } } } } return currentTotal; } /** * This method renders each cell using already set actual column widths. It * sets minimum row heights based on this. * * @param hasBorder * the has border * @param cellSpacingX * the cell spacing x * @param cellSpacingY * the cell spacing y */ private final void preLayout(int hasBorder, int cellSpacingX, int cellSpacingY/* , boolean tableWidthKnown */) { // TODO: Fix for table without width that has a subtable with // width=100%. // TODO: Maybe it can be addressed when NOWRAP is implemented. // TODO: Maybe it's possible to eliminate this pre-layout altogether. SizeInfo[] colSizes = this.columnSizes; SizeInfo[] rowSizes = this.rowSizes; // Initialize minSize in rows int numRows = rowSizes.length; for (int i = 0; i < numRows; i++) { rowSizes[i].setMinSize(0); } // Initialize layoutSize in columns int numCols = colSizes.length; for (int i = 0; i < numCols; i++) { colSizes[i].setLayoutSize(0); } ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); if (caption != null) { caption.doLayout(0, 0, true, true, null, 0, 0, true, true); captionSize.setHeight(caption.height); captionSize.setWidth(caption.width); } for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); int col = cell.getVirtualColumn(); int colSpan = cell.getColSpan(); int cellsTotalWidth; int cellsUsedWidth; boolean widthDeclared = false; if (colSpan > 1) { cellsUsedWidth = 0; for (int x = 0; x < colSpan; x++) { SizeInfo colSize = colSizes[col + x]; if (colSize.getHtmlLength() != null) { widthDeclared = true; } cellsUsedWidth += colSize.getActualSize(); } cellsTotalWidth = cellsUsedWidth + ((colSpan - 1) * (cellSpacingX + (2 * hasBorder))); } else { SizeInfo colSize = colSizes[col]; if (colSize.getHtmlLength() != null) { widthDeclared = true; } cellsUsedWidth = cellsTotalWidth = colSize.getActualSize(); } // TODO: A tentative height could be used here: Height of // table divided by number of rows. java.awt.Dimension size; RenderThreadState state = RenderThreadState.getState(); boolean prevOverrideNoWrap = state.overrideNoWrap; try { if (!prevOverrideNoWrap) { state.overrideNoWrap = !widthDeclared; } size = cell.doCellLayout(cellsTotalWidth, 0, true, true, true); } finally { state.overrideNoWrap = prevOverrideNoWrap; } // Set render widths int cellLayoutWidth = size.width; if (colSpan > 1) { if (cellsUsedWidth > 0) { double ratio = (double) cellLayoutWidth / cellsUsedWidth; for (int x = 0; x < colSpan; x++) { SizeInfo si = colSizes[col + x]; int newLayoutSize = (int) Math.round(si.getActualSize() * ratio); if (si.getLayoutSize() < newLayoutSize) { si.setLayoutSize(newLayoutSize); } } } else { int newLayoutSize = cellLayoutWidth / colSpan; for (int x = 0; x < colSpan; x++) { SizeInfo si = colSizes[col + x]; if (si.getLayoutSize() < newLayoutSize) { si.setLayoutSize(newLayoutSize); } } } } else { SizeInfo colSizeInfo = colSizes[col]; if (colSizeInfo.getLayoutSize() < cellLayoutWidth) { colSizeInfo.setLayoutSize(cellLayoutWidth); } } // Set minimum heights int actualCellHeight = size.height; int row = cell.getVirtualRow(); int rowSpan = cell.getRowSpan(); if (rowSpan > 1) { int vch = (actualCellHeight - ((rowSpan - 1) * (cellSpacingY + (2 * hasBorder)))) / rowSpan; for (int y = 0; y < rowSpan; y++) { if (rowSizes[row + y].getMinSize() < vch) { rowSizes[row + y].setMinSize(vch); } } } else { if (rowSizes[row].getMinSize() < actualCellHeight) { rowSizes[row].setMinSize(actualCellHeight); } } } } /** * Determine row sizes. * * @param hasBorder * the has border * @param cellSpacing * the cell spacing * @param availHeight * the avail height * @param sizeOnly * the size only */ private void determineRowSizes(int hasBorder, int cellSpacing, int availHeight, boolean sizeOnly) { HtmlLength tableHeightLength = TableMatrix.getHeightLength(this.tableElement, availHeight); int tableHeight; if (tableHeightLength != null) { tableHeight = tableHeightLength.getLength(availHeight); this.determineRowSizesFixedTH(hasBorder, cellSpacing, availHeight, tableHeight, sizeOnly); } else { // zbytocne // tableHeight = heightsOfExtras; // for(int row = 0; row < numRows; row++) { // tableHeight += rowSizes[row].minSize; // } this.determineRowSizesFlexibleTH(hasBorder, cellSpacing, sizeOnly); } } /** * Determine row sizes fixed th. * * @param hasBorder * the has border * @param cellSpacing * the cell spacing * @param availHeight * the avail height * @param tableHeight * the table height * @param sizeOnly * the size only */ private void determineRowSizesFixedTH(int hasBorder, int cellSpacing, int availHeight, int tableHeight, boolean sizeOnly) { SizeInfo[] rowSizes = this.rowSizes; int numRows = rowSizes.length; int heightsOfExtras = this.heightsOfExtras; int cellAvailHeight = tableHeight - heightsOfExtras; if (cellAvailHeight < 0) { cellAvailHeight = 0; } // Look at percentages first int heightUsedbyPercent = 0; int otherMinSize = 0; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if ((heightLength != null) && (heightLength.getLengthType() == HtmlLength.LENGTH)) { int actualSizeInt = heightLength.getLength(cellAvailHeight); if (actualSizeInt < rowSizeInfo.getMinSize()) { actualSizeInt = rowSizeInfo.getMinSize(); } heightUsedbyPercent += actualSizeInt; rowSizeInfo.setActualSize(actualSizeInt); } else { otherMinSize += rowSizeInfo.getMinSize(); } } // Check if rows with percent are bigger than they should be if ((heightUsedbyPercent + otherMinSize) > cellAvailHeight) { double ratio = (double) (cellAvailHeight - otherMinSize) / heightUsedbyPercent; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if ((heightLength != null) && (heightLength.getLengthType() == HtmlLength.LENGTH)) { int actualSize = rowSizeInfo.getActualSize(); int prevActualSize = actualSize; int newActualSize = (int) Math.round(prevActualSize * ratio); if (newActualSize < rowSizeInfo.getMinSize()) { newActualSize = rowSizeInfo.getMinSize(); } heightUsedbyPercent += (newActualSize - prevActualSize); rowSizeInfo.setActualSize(newActualSize); } } } // Look at rows with absolute sizes int heightUsedByAbsolute = 0; int noHeightMinSize = 0; int numNoHeightColumns = 0; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if ((heightLength != null) && (heightLength.getLengthType() != HtmlLength.LENGTH)) { // TODO: MULTI-LENGTH not supported int actualSizeInt = heightLength.getRawValue(); if (actualSizeInt < rowSizeInfo.getMinSize()) { actualSizeInt = rowSizeInfo.getMinSize(); } heightUsedByAbsolute += actualSizeInt; rowSizeInfo.setActualSize(actualSizeInt); } else if (heightLength == null) { numNoHeightColumns++; noHeightMinSize += rowSizeInfo.getMinSize(); } } // numNoHeightColumns++; // noHeightMinSize += captionSize.height.actualSize; // Check if absolute sizing is too much if ((heightUsedByAbsolute + heightUsedbyPercent + noHeightMinSize) > cellAvailHeight) { double ratio = (double) (cellAvailHeight - noHeightMinSize - heightUsedbyPercent) / heightUsedByAbsolute; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if ((heightLength != null) && (heightLength.getLengthType() != HtmlLength.LENGTH)) { int actualSize = rowSizeInfo.getActualSize(); int prevActualSize = actualSize; int newActualSize = (int) Math.round(prevActualSize * ratio); if (newActualSize < rowSizeInfo.getMinSize()) { newActualSize = rowSizeInfo.getMinSize(); } heightUsedByAbsolute += (newActualSize - prevActualSize); rowSizeInfo.setActualSize(newActualSize); } } } // Assign all rows without heights now int remainingHeight = cellAvailHeight - heightUsedByAbsolute - heightUsedbyPercent; int heightUsedByRemaining = 0; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if (heightLength == null) { int actualSizeInt = remainingHeight / numNoHeightColumns; if (actualSizeInt < rowSizeInfo.getMinSize()) { actualSizeInt = rowSizeInfo.getMinSize(); } heightUsedByRemaining += actualSizeInt; rowSizeInfo.setActualSize(actualSizeInt); } } if (captionSize != null /* && captionSize.height.htmlLength == null */) { // captionSize.height.actualSize = captionSize.height.minSize; heightUsedByRemaining += captionSize.getHeight(); } // Calculate actual table width int totalUsed = heightUsedByAbsolute + heightUsedbyPercent + heightUsedByRemaining; if (totalUsed >= cellAvailHeight) { this.tableHeight = totalUsed + heightsOfExtras; } else { // Rows too short; expand them double ratio = (double) cellAvailHeight / totalUsed; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; int actualSize = rowSizeInfo.getActualSize(); rowSizeInfo.setActualSize((int) Math.round(actualSize * ratio)); } this.tableHeight = tableHeight; } // TODO: // This final render is probably unnecessary. Avoid exponential // rendering // by setting a single height of subcell. Verify that IE only sets // height // of subcells when height of row or table are specified. this.finalRender(hasBorder, cellSpacing, sizeOnly); } /** * Determine row sizes flexible th. * * @param hasBorder * the has border * @param cellSpacing * the cell spacing * @param sizeOnly * the size only */ private void determineRowSizesFlexibleTH(int hasBorder, int cellSpacing, boolean sizeOnly) { SizeInfo[] rowSizes = this.rowSizes; // SizeInfo captionSizeHeight = this.captionSize.height; int numRows = rowSizes.length; // int heightsOfExtras = this.heightsOfExtras; // Look at rows with absolute sizes int heightUsedByAbsolute = 0; int percentSum = 0; for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if ((heightLength != null) && (heightLength.getLengthType() == HtmlLength.PIXELS)) { // TODO: MULTI-LENGTH not supported int actualSizeInt = heightLength.getRawValue(); if (actualSizeInt < rowSizeInfo.getMinSize()) { actualSizeInt = rowSizeInfo.getMinSize(); } heightUsedByAbsolute += actualSizeInt; rowSizeInfo.setActualSize(actualSizeInt); } else if ((heightLength != null) && (heightLength.getLengthType() == HtmlLength.LENGTH)) { percentSum += heightLength.getRawValue(); } } // Look at rows with no specified heights int heightUsedByNoSize = 0; // Set sizes to in row height for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength widthLength = rowSizeInfo.getHtmlLength(); if (widthLength == null) { int actualSizeInt = rowSizeInfo.getMinSize(); heightUsedByNoSize += actualSizeInt; rowSizeInfo.setActualSize(actualSizeInt); } } /* * if(this.captionSize != null){int actualSizeInt = * this.captionSize.height.minSize; heightUsedByNoSize += actualSizeInt; * this.captionSize.height.actualSize = actualSizeInt;} */ // Calculate actual total cell width int expectedTotalCellHeight = (int) Math .round((heightUsedByAbsolute + heightUsedByNoSize) / (1 - (percentSum / 100.0))); for (int i = 0; i < numRows; i++) { SizeInfo rowSizeInfo = rowSizes[i]; HtmlLength heightLength = rowSizeInfo.getHtmlLength(); if ((heightLength != null) && (heightLength.getLengthType() == HtmlLength.LENGTH)) { int actualSizeInt = heightLength.getLength(expectedTotalCellHeight); if (actualSizeInt < rowSizeInfo.getMinSize()) { actualSizeInt = rowSizeInfo.getMinSize(); } rowSizeInfo.setActualSize(actualSizeInt); } } // Do a final render to set actual cell sizes this.finalRender(hasBorder, cellSpacing, sizeOnly); } /** * This method renders each cell using already set actual column widths. It * sets minimum row heights based on this. * * @param hasBorder * the has border * @param cellSpacing * the cell spacing * @param sizeOnly * the size only */ private final void finalRender(int hasBorder, int cellSpacing, boolean sizeOnly) { // finalRender needs to adjust actualSize of columns and rows // given that things might change as we render one last time. ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; SizeInfo[] colSizes = this.columnSizes; SizeInfo[] rowSizes = this.rowSizes; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); int col = cell.getVirtualColumn(); int colSpan = cell.getColSpan(); int totalCellWidth; if (colSpan > 1) { totalCellWidth = (colSpan - 1) * (cellSpacing + (2 * hasBorder)); for (int x = 0; x < colSpan; x++) { totalCellWidth += colSizes[col + x].getActualSize(); } } else { totalCellWidth = colSizes[col].getActualSize(); } int row = cell.getVirtualRow(); int rowSpan = cell.getRowSpan(); int totalCellHeight; if (rowSpan > 1) { totalCellHeight = (rowSpan - 1) * (cellSpacing + (2 * hasBorder)); for (int y = 0; y < rowSpan; y++) { totalCellHeight += rowSizes[row + y].getActualSize(); } } else { totalCellHeight = rowSizes[row].getActualSize(); } Dimension size = cell.doCellLayout(totalCellWidth, totalCellHeight, true, true, sizeOnly); if (size.width > totalCellWidth) { if (colSpan == 1) { colSizes[col].setActualSize(size.width); } else { colSizes[col].setActualSize(colSizes[col].getActualSize() + (size.width - totalCellWidth)); } } if (size.height > totalCellHeight) { if (rowSpan == 1) { rowSizes[row].setActualSize(size.width); } else { rowSizes[row].setActualSize(colSizes[col].getActualSize() + (size.width - totalCellWidth)); } } } if (this.captionSize != null) { this.caption.doLayout(this.captionSize.getWidth(), this.captionSize.getHeight(), true, true, null, RenderState.OVERFLOW_NONE, RenderState.OVERFLOW_NONE, sizeOnly, true); } } /** * Sets bounds of each cell's component, and sumps up table width and * height. * * @param insets * the insets */ public final void doLayout(Insets insets) { // Set row offsets SizeInfo[] rowSizes = this.rowSizes; int numRows = rowSizes.length; int yoffset = insets.top; int cellSpacingY = this.cellSpacingY; int hasBorder = this.hasOldStyleBorder; if ((this.captionSize != null) && !isCaptionBotton()) { yoffset += this.captionSize.getHeight(); yoffset += hasBorder; } for (int i = 0; i < numRows; i++) { yoffset += cellSpacingY; yoffset += hasBorder; SizeInfo rowSizeInfo = rowSizes[i]; rowSizeInfo.setOffset(yoffset); yoffset += rowSizeInfo.getActualSize(); yoffset += hasBorder; } if ((this.captionSize != null) && isCaptionBotton()) { this.captionSize.setHeightOffset(yoffset + insets.bottom); yoffset += this.captionSize.getHeight(); yoffset += hasBorder; } this.tableHeight = yoffset + cellSpacingY + insets.bottom; // Set colum offsets SizeInfo[] colSizes = this.columnSizes; int numColumns = colSizes.length; int xoffset = insets.left; int cellSpacingX = this.cellSpacingX; for (int i = 0; i < numColumns; i++) { xoffset += cellSpacingX; xoffset += hasBorder; SizeInfo colSizeInfo = colSizes[i]; colSizeInfo.setOffset(xoffset); xoffset += colSizeInfo.getActualSize(); xoffset += hasBorder; } this.tableWidth = xoffset + cellSpacingX + insets.right; // Set offsets of each cell ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); cell.setCellBounds(colSizes, rowSizes, hasBorder, cellSpacingX, cellSpacingY); } if (this.caption != null) { this.caption.setBounds(0, this.captionSize.getHeightOffset(), this.tableWidth, this.captionSize.getHeight()); } } /** * Checks if is caption botton. * * @return true, if is caption botton */ private boolean isCaptionBotton() { if (this.captionElement != null) { AbstractCSS2Properties props = captionElement.getCurrentStyle(); String captionSide = props == null ? null : props.getCaptionSide(); if (props == null) { captionSide = this.captionElement.getCaptionSide(); } if (captionSide == null) { return false; } else { return "bottom".equals(captionSide); } } return false; } /** * Paint. * * @param g * the g * @param size * the size */ public final void paint(Graphics g, Dimension size) { ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); // Should clip table cells, just in case. // just test Graphics newG = g.create(cell.x, cell.y, cell.width, cell.height); try { cell.paint(newG); } finally { newG.dispose(); } } if (this.caption != null) { Graphics newG = g.create(this.caption.x, this.caption.y, this.caption.width, this.caption.height); try { this.caption.paint(newG); } finally { newG.dispose(); } } if (this.hasOldStyleBorder > 0) { g.setColor(Color.GRAY); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); int cx = cell.getX() - 1; int cy = cell.getY() - 1; int cwidth = cell.getWidth() + 1; int cheight = cell.getHeight() + 1; g.drawRect(cx, cy, cwidth, cheight); } } } /* * (non-Javadoc) * * @see * org.lobobrowser.html.render.BoundableRenderable#getRenderablePoint(int, * int) */ /** * Gets the lowest renderable spot. * * @param x * the x * @param y * the y * @return the lowest renderable spot */ public RenderableSpot getLowestRenderableSpot(int x, int y) { ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); Rectangle bounds = cell.getBounds(); if (bounds.contains(x, y)) { RenderableSpot rp = cell.getLowestRenderableSpot(x - bounds.x, y - bounds.y); if (rp != null) { return rp; } } } return null; } /* * (non-Javadoc) * * @see * org.lobobrowser.html.render.BoundableRenderable#onMouseClick(java.awt. * event .MouseEvent, int, int) */ /** * On mouse click. * * @param event * the event * @param x * the x * @param y * the y * @return true, if successful */ public boolean onMouseClick(MouseEvent event, int x, int y) { ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); Rectangle bounds = cell.getBounds(); if (bounds.contains(x, y)) { if (!cell.onMouseClick(event, x - bounds.x, y - bounds.y)) { return false; } break; } } return true; } /** * On double click. * * @param event * the event * @param x * the x * @param y * the y * @return true, if successful */ public boolean onDoubleClick(MouseEvent event, int x, int y) { ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); Rectangle bounds = cell.getBounds(); if (bounds.contains(x, y)) { if (!cell.onDoubleClick(event, x - bounds.x, y - bounds.y)) { return false; } break; } } return true; } /** The armed renderable. */ private BoundableRenderable armedRenderable; /* * (non-Javadoc) * * @see * org.lobobrowser.html.render.BoundableRenderable#onMouseDisarmed(java.awt * .event.MouseEvent) */ /** * On mouse disarmed. * * @param event * the event * @return true, if successful */ public boolean onMouseDisarmed(MouseEvent event) { BoundableRenderable ar = this.armedRenderable; if (ar != null) { this.armedRenderable = null; return ar.onMouseDisarmed(event); } else { return true; } } /* * (non-Javadoc) * * @see * org.lobobrowser.html.render.BoundableRenderable#onMousePressed(java.awt. * event.MouseEvent, int, int) */ /** * On mouse pressed. * * @param event * the event * @param x * the x * @param y * the y * @return true, if successful */ public boolean onMousePressed(MouseEvent event, int x, int y) { ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); Rectangle bounds = cell.getBounds(); if (bounds.contains(x, y)) { if (!cell.onMousePressed(event, x - bounds.x, y - bounds.y)) { this.armedRenderable = cell; return false; } break; } } return true; } /* * (non-Javadoc) * * @see * org.lobobrowser.html.render.BoundableRenderable#onMouseReleased(java.awt * .event.MouseEvent, int, int) */ /** * On mouse released. * * @param event * the event * @param x * the x * @param y * the y * @return true, if successful */ public boolean onMouseReleased(MouseEvent event, int x, int y) { ArrayList<BoundableRenderable> allCells = this.ALL_CELLS; int numCells = allCells.size(); boolean found = false; for (int i = 0; i < numCells; i++) { RTableCell cell = (RTableCell) allCells.get(i); Rectangle bounds = cell.getBounds(); if (bounds.contains(x, y)) { found = true; BoundableRenderable oldArmedRenderable = this.armedRenderable; if ((oldArmedRenderable != null) && (cell != oldArmedRenderable)) { oldArmedRenderable.onMouseDisarmed(event); this.armedRenderable = null; } if (!cell.onMouseReleased(event, x - bounds.x, y - bounds.y)) { return false; } break; } } if (!found) { BoundableRenderable oldArmedRenderable = this.armedRenderable; if (oldArmedRenderable != null) { oldArmedRenderable.onMouseDisarmed(event); this.armedRenderable = null; } } return true; } /** * Gets the renderables. * * @return the renderables */ public Iterator<BoundableRenderable> getRenderables() { return this.ALL_CELLS.iterator(); } }