0001: /**
0002: * ===========================================
0003: * JFreeReport : a free Java reporting library
0004: * ===========================================
0005: *
0006: * Project Info: http://reporting.pentaho.org/
0007: *
0008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
0009: *
0010: * This library is free software; you can redistribute it and/or modify it under the terms
0011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
0012: * either version 2.1 of the License, or (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
0015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0016: * See the GNU Lesser General Public License for more details.
0017: *
0018: * You should have received a copy of the GNU Lesser General Public License along with this
0019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
0020: * Boston, MA 02111-1307, USA.
0021: *
0022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0023: * in the United States and other countries.]
0024: *
0025: * ------------
0026: * SheetLayout.java
0027: * ------------
0028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
0029: */package org.jfree.report.modules.output.table.base;
0030:
0031: import java.awt.Color;
0032: import java.awt.geom.Ellipse2D;
0033: import java.awt.geom.Line2D;
0034: import java.awt.geom.Rectangle2D;
0035: import java.awt.geom.RoundRectangle2D;
0036: import java.util.ArrayList;
0037:
0038: import org.jfree.report.layout.model.Border;
0039: import org.jfree.report.layout.model.BorderCorner;
0040: import org.jfree.report.layout.model.BorderEdge;
0041: import org.jfree.report.layout.model.CanvasRenderBox;
0042: import org.jfree.report.layout.model.RenderBox;
0043: import org.jfree.report.layout.model.RenderNode;
0044: import org.jfree.report.layout.model.RenderableReplacedContent;
0045: import org.jfree.report.layout.model.context.BoxDefinition;
0046: import org.jfree.report.layout.process.ProcessUtility;
0047: import org.jfree.report.style.ElementStyleKeys;
0048: import org.jfree.report.style.StyleSheet;
0049: import org.jfree.report.util.InstanceID;
0050: import org.jfree.report.util.geom.StrictBounds;
0051: import org.jfree.report.util.geom.StrictGeomUtility;
0052: import org.jfree.util.Log;
0053:
0054: /**
0055: * The sheet layout is used to build the background map and to collect the x- and y-cell-borders.
0056: */
0057: public class SheetLayout {
0058: /**
0059: * How backgrounds for cells get computed
0060: * --------------------------------------
0061: *
0062: * JFreeReport handles 4 background types:
0063: *
0064: * Bands
0065: * -----
0066: * Bands are no real backgrounds, as they do not influence the output,
0067: * but they will be used to simplify the computation.
0068: *
0069: * Rectangles
0070: * ----------
0071: * Define the cell background (fill = true) and all 4 borders of a cell
0072: * (draw == true).
0073: *
0074: * Horizontal & Vertical Lines
0075: * ----------------
0076: * These lines define the Top or Left border or a cell. Bottom and right
0077: * borders get mapped into the Top/Left borders of the next cells.
0078: *
0079: * Joining
0080: * -------
0081: * When a background element is added, JFreeReport first checks, whether
0082: * a background has been already defined for that cell position. If not,
0083: * the given background is used as is.
0084: *
0085: * If there is a background defined, a merge operation starts. JFreeReport
0086: * will try to join both background definitions.
0087: * (This is done while adding the TableCellBackground to the SheetLayout)
0088: *
0089: * New Elements overwrite old elements. That means if there are two
0090: * conflicting borders or backgrounds at a given position, any old border
0091: * or background will be replaced as soon as a more current value appears.
0092: *
0093: * Lines, which make up the bottom most or right most borders, are held in
0094: * a zero-width column or zero-height row. These columns are always there,
0095: * if there is at least one background reaching to right or bottom of the report.
0096: * A flag indicates, whether these cells are significant. (for validity this
0097: * flag should mirror the result of an test "All Cells in these Row/Column are
0098: * empty".
0099: *
0100: * These lines are not mapped into bottom cell lines, as the resulting
0101: * merge would not be predictable and would depend on the order of the
0102: * split operations. A predictable merge implementation would be by far
0103: * more complex than this 'hack'.
0104: *
0105: * To create a consistent behaviour, rectangle-borders behave like four lines;
0106: * therefore the bottom and right border of the rectangle will be mapped into
0107: * top or left border cells.
0108: */
0109:
0110: /**
0111: * An internal flag indicating that the upper or left bounds should be used.
0112: */
0113: private static final boolean UPPER_BOUNDS = true;
0114: private static final boolean LOWER_BOUNDS = false;
0115:
0116: private static class CellReference {
0117: private long x;
0118: private long y;
0119: private long width;
0120: private long height;
0121: private InstanceID contentID;
0122: private String node;
0123:
0124: public CellReference(final RenderNode node, final long shift) {
0125: this .node = node.toString();
0126: this .width = node.getWidth();
0127: this .y = node.getY() + shift;
0128: this .x = node.getX();
0129: this .height = node.getHeight();
0130: this .contentID = node.getInstanceId();
0131: }
0132:
0133: public long getX() {
0134: return x;
0135: }
0136:
0137: public long getY() {
0138: return y;
0139: }
0140:
0141: public long getWidth() {
0142: return width;
0143: }
0144:
0145: public long getHeight() {
0146: return height;
0147: }
0148:
0149: public InstanceID getContentID() {
0150: return contentID;
0151: }
0152:
0153: public String toString() {
0154: return "CellReference{" + "x=" + x + ", y=" + y
0155: + ", width=" + width + ", height=" + height
0156: + ", contentID=" + contentID + ", node='" + node
0157: + '\'' + '}';
0158: }
0159: }
0160:
0161: /**
0162: * A flag, defining whether to use strict layout mode.
0163: */
0164: private final boolean strict;
0165:
0166: /**
0167: * The XBounds, all vertical cell boundaries (as CoordinateMappings).
0168: */
0169: private final TableCutList xBounds;
0170:
0171: /**
0172: * The YBounds, all vertical cell boundaries (as CoordinateMappings).
0173: */
0174: private final TableCutList yBounds;
0175:
0176: /**
0177: * Is a list of lists, contains the merged backgrounds ...
0178: */
0179: private GenericObjectTable backend;
0180: /**
0181: * Contains the references to the original data passed into this layouter.
0182: */
0183: private GenericObjectTable objectIdTable;
0184:
0185: /**
0186: * The right border of the grid. This is needed when not being in the strict mode.
0187: */
0188: private long xMaxBounds;
0189: private long yMaxBounds;
0190: private transient StrictBounds workBounds;
0191: private boolean ellipseAsRectangle;
0192: private boolean verboseCellMarker;
0193:
0194: /**
0195: * Creates a new TableGrid-object. If strict mode is enabled, all cell bounds are used to create the table grid,
0196: * resulting in a more complex layout.
0197: *
0198: * @param strict the strict mode for the layout.
0199: */
0200: public SheetLayout(final boolean strict,
0201: final boolean ellipseAsRectangle,
0202: final boolean verboseCellMarker) {
0203: this .ellipseAsRectangle = ellipseAsRectangle;
0204: this .xBounds = new TableCutList(50);
0205: this .yBounds = new TableCutList(200);
0206: this .strict = strict;
0207: this .xMaxBounds = 0;
0208: this .yMaxBounds = 0;
0209: this .backend = new GenericObjectTable(200, 5);
0210: this .objectIdTable = new GenericObjectTable(200, 5);
0211: this .verboseCellMarker = verboseCellMarker;
0212: this .ensureXMapping(0, false);
0213: this .ensureYMapping(0, false);
0214: }
0215:
0216: private TableCellDefinition createBackground(final RenderBox box,
0217: final long shift) {
0218: final TableCellDefinition legacyDefinition = computeLegacyBackground(
0219: box, shift);
0220: if (legacyDefinition != null) {
0221: return legacyDefinition;
0222: }
0223:
0224: if (box.getBoxDefinition().getBorder().isEmpty() == false) {
0225: return new TableCellDefinition(box, shift);
0226: }
0227:
0228: final StyleSheet styleSheet = box.getStyleSheet();
0229: if (styleSheet
0230: .getStyleProperty(ElementStyleKeys.BACKGROUND_COLOR) != null) {
0231: return new TableCellDefinition(box, shift);
0232: }
0233:
0234: if (styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME) != null) {
0235: return new TableCellDefinition(box, shift);
0236: }
0237: return null;
0238: }
0239:
0240: private TableCellDefinition computeLegacyBackground(
0241: final RenderBox box, final long shift) {
0242:
0243: // For legacy reasons: A single ReplacedContent in a canvas means, we may have a old-style border and
0244: // background definition.
0245: if (box instanceof CanvasRenderBox == false) {
0246: return null;
0247: }
0248:
0249: final RenderNode firstChild = box.getFirstChild();
0250: if (firstChild != box.getLastChild()) {
0251: return null;
0252: }
0253:
0254: if (firstChild instanceof RenderableReplacedContent == false) {
0255: return null;
0256: }
0257:
0258: final StyleSheet styleSheet = box.getStyleSheet();
0259: final RenderableReplacedContent rpc = (RenderableReplacedContent) firstChild;
0260: final Object rawObject = rpc.getRawObject();
0261:
0262: final boolean draw = styleSheet
0263: .getBooleanStyleProperty(ElementStyleKeys.DRAW_SHAPE);
0264: if (rawObject instanceof Line2D && draw) {
0265: final TableCellDefinition tableCellDefinition = new TableCellDefinition(
0266: box, shift);
0267: tableCellDefinition.setLineHint((Line2D) rawObject);
0268: return tableCellDefinition;
0269: }
0270:
0271: if (rawObject instanceof Rectangle2D
0272: || (ellipseAsRectangle && rawObject instanceof Ellipse2D)) {
0273: final TableCellDefinition tableCellDefinition = new TableCellDefinition(
0274: box, shift);
0275: if (draw) {
0276: // the beast has a border ..
0277: final BorderEdge edge = ProcessUtility
0278: .produceBorderEdge(box.getStyleSheet());
0279: if (edge != null) {
0280: tableCellDefinition.setTop(edge);
0281: tableCellDefinition.setLeft(edge);
0282: tableCellDefinition.setBottom(edge);
0283: tableCellDefinition.setRight(edge);
0284: }
0285: }
0286: if (styleSheet
0287: .getBooleanStyleProperty(ElementStyleKeys.FILL_SHAPE)) {
0288: tableCellDefinition
0289: .setBackgroundColor((Color) styleSheet
0290: .getStyleProperty(ElementStyleKeys.PAINT));
0291: }
0292: return tableCellDefinition;
0293: }
0294:
0295: if (rawObject instanceof RoundRectangle2D) {
0296: final TableCellDefinition tableCellDefinition = new TableCellDefinition(
0297: box, shift);
0298: if (draw) {
0299: // the beast has a border ..
0300: final BorderEdge edge = ProcessUtility
0301: .produceBorderEdge(box.getStyleSheet());
0302: if (edge != null) {
0303: tableCellDefinition.setTop(edge);
0304: tableCellDefinition.setLeft(edge);
0305: tableCellDefinition.setBottom(edge);
0306: tableCellDefinition.setRight(edge);
0307: }
0308: }
0309: if (styleSheet
0310: .getBooleanStyleProperty(ElementStyleKeys.FILL_SHAPE)) {
0311: tableCellDefinition
0312: .setBackgroundColor((Color) styleSheet
0313: .getStyleProperty(ElementStyleKeys.PAINT));
0314: }
0315: final RoundRectangle2D rr = (RoundRectangle2D) rawObject;
0316: final long arcHeight = StrictGeomUtility.toInternalValue(rr
0317: .getArcHeight());
0318: final long arcWidth = StrictGeomUtility.toInternalValue(rr
0319: .getArcWidth());
0320: if (arcHeight > 0 && arcWidth > 0) {
0321: final BorderCorner bc = new BorderCorner(arcWidth,
0322: arcHeight);
0323: tableCellDefinition.setTopLeft(bc);
0324: tableCellDefinition.setBottomLeft(bc);
0325: tableCellDefinition.setTopRight(bc);
0326: tableCellDefinition.setBottomRight(bc);
0327: }
0328: return tableCellDefinition;
0329: }
0330:
0331: return null;
0332: }
0333:
0334: /**
0335: * Adds the bounds of the given TableCellData to the grid. The bounds given must be the same as the bounds of the
0336: * element, or the layouting might produce surprising results.
0337: * <p/>
0338: * This method will do nothing, if the element has a width and height of zero and does not define any anchors.
0339: *
0340: * @param element the position that should be added to the grid (might be null).
0341: * @param shift the vertical shift which adjusts the visual position of the content.
0342: * @throws NullPointerException if the bounds are null
0343: */
0344: public void add(final RenderBox element, final long shift) {
0345: final long shiftedY = element.getY() + shift;
0346: final long elementY;
0347: if (shiftedY < 0) {
0348: if ((shiftedY + element.getHeight()) < 0) {
0349: // The box will not be visible at all. (Should not happen in a sane environment ..)
0350: Log.debug("THIS BOX WILL BE INVISIBLE: " + element);
0351: return;
0352: }
0353:
0354: elementY = 0;
0355: } else {
0356: elementY = shiftedY;
0357: }
0358:
0359: final TableCellDefinition background = createBackground(
0360: element, shift);
0361: if (addLine(element, background, shift, elementY, shiftedY)) {
0362: return;
0363: }
0364:
0365: final long elementX = element.getX();
0366: final long elementRightX = (element.getWidth() + elementX);
0367: final long elementBottomY = element.getHeight() + shiftedY;
0368:
0369: // collect the bounds and add them to the xBounds and yBounds collection
0370: // if necessary...
0371: ensureXMapping(elementX, false);
0372: ensureYMapping(elementY, false);
0373:
0374: // an end cut is auxilary, if it is not a background and the layout is not strict
0375: final boolean aux = (background == null)
0376: && (isStrict() == false);
0377: ensureXMapping(elementRightX, aux);
0378: ensureYMapping(elementBottomY, aux);
0379:
0380: // update the collected maximums
0381: if (xMaxBounds < elementRightX) {
0382: xMaxBounds = elementRightX;
0383: }
0384: if (yMaxBounds < elementBottomY) {
0385: yMaxBounds = elementBottomY;
0386: }
0387:
0388: // now add the new element to the table ...
0389: // the +1 makes sure, that we include the right and bottom element borders in the set
0390: final int lowerXIndex = xBounds.findKeyPosition(elementX,
0391: SheetLayout.LOWER_BOUNDS);
0392: final int upperXIndex = xBounds.findKeyPosition(elementRightX,
0393: SheetLayout.UPPER_BOUNDS);
0394:
0395: final int lowerYIndex = yBounds.findKeyPosition(elementY,
0396: SheetLayout.LOWER_BOUNDS);
0397: final int upperYIndex = yBounds.findKeyPosition(elementBottomY,
0398: SheetLayout.UPPER_BOUNDS);
0399:
0400: if (background != null) {
0401: // this does nothing for yLength == 1 && xLength == 1
0402: // in that case, the whole thing did not define an area but a
0403: // vertical or horizontal line.
0404: processAreaBackground(lowerXIndex, lowerYIndex,
0405: upperXIndex, upperYIndex, background);
0406: }
0407:
0408: if (ProcessUtility.isContent(element, false,
0409: ellipseAsRectangle, false) == false) {
0410: return;
0411: }
0412:
0413: final boolean hasVerticalPaddings;
0414: final BoxDefinition sblp = element.getBoxDefinition();
0415: if (sblp.getPaddingTop() != 0 || sblp.getPaddingBottom() != 0) {
0416: final long coordinate = elementBottomY
0417: - sblp.getPaddingBottom();
0418: if (coordinate > 0) {
0419: ensureYMapping(coordinate, false);
0420: if (shiftedY >= 0) {
0421: ensureYMapping(elementY + sblp.getPaddingTop(),
0422: false);
0423: }
0424: hasVerticalPaddings = true;
0425: } else {
0426: hasVerticalPaddings = false;
0427: }
0428: } else {
0429: hasVerticalPaddings = false;
0430: }
0431:
0432: final boolean hasHorizontalPaddings;
0433: if (sblp.getPaddingLeft() != 0 || sblp.getPaddingRight() != 0) {
0434: // check if the element is a page-spanning element. No top-paddings apply in that case..
0435: ensureXMapping(elementX + sblp.getPaddingLeft(), false);
0436: ensureXMapping(elementRightX - sblp.getPaddingRight(),
0437: false);
0438: hasHorizontalPaddings = true;
0439: } else {
0440: hasHorizontalPaddings = false;
0441: }
0442:
0443: if (hasHorizontalPaddings == false
0444: && hasVerticalPaddings == false) {
0445: // now, elements can be both content and background.
0446: // mark cells as occupied ..
0447: if (isCellAreaOccupied(lowerXIndex, lowerYIndex,
0448: upperXIndex, upperYIndex, element.getName()) == false) {
0449: final Object cellReference;
0450: if (verboseCellMarker) {
0451: cellReference = new SheetLayout.CellReference(
0452: element, shift);
0453: } else {
0454: cellReference = element.getInstanceId();
0455: }
0456:
0457: final int maxX = Math.max(lowerXIndex + 1, upperXIndex);
0458: final int maxY = Math.max(lowerYIndex + 1, upperYIndex);
0459: for (int y = lowerYIndex; y < maxY; y++) {
0460: // get the index of the current row in the backend-table ...
0461: final TableCutList.CutEntry currentRowValue = yBounds
0462: .getValueAt(y);
0463: final int currentRowIndex = currentRowValue
0464: .getPosition();
0465:
0466: // for every row we iterate over all columns ...
0467: // but we do not touch the last column ..
0468: for (int x = lowerXIndex; x < maxX; x++) {
0469: // again get the column index for the backend table ...
0470: final TableCutList.CutEntry currentColumnValue = xBounds
0471: .getValueAt(x);
0472: final int currentColumnIndex = currentColumnValue
0473: .getPosition();
0474:
0475: objectIdTable.setObject(currentRowIndex,
0476: currentColumnIndex, cellReference);
0477: }
0478: }
0479: }
0480: } else {
0481: final int lowerXPadIndex = xBounds.findKeyPosition(elementX
0482: + sblp.getPaddingLeft(), SheetLayout.LOWER_BOUNDS);
0483: final int upperXPadIndex = xBounds.findKeyPosition(
0484: elementRightX + sblp.getPaddingRight(),
0485: SheetLayout.UPPER_BOUNDS);
0486:
0487: final int lowerYPadIndex = yBounds.findKeyPosition(elementY
0488: + sblp.getPaddingTop(), SheetLayout.LOWER_BOUNDS);
0489: final int upperYPadIndex = yBounds.findKeyPosition(
0490: elementBottomY - sblp.getPaddingBottom(),
0491: SheetLayout.UPPER_BOUNDS);
0492:
0493: if (isCellAreaOccupied(lowerXPadIndex, lowerYPadIndex,
0494: upperXPadIndex, upperYPadIndex, element.getName()) == false) {
0495: final Object cellReference;
0496: if (verboseCellMarker) {
0497: cellReference = new SheetLayout.CellReference(
0498: element, shift);
0499: } else {
0500: cellReference = element.getInstanceId();
0501: }
0502:
0503: final int maxX = Math.max(lowerXPadIndex + 1,
0504: upperXPadIndex);
0505: final int maxY = Math.max(lowerYPadIndex + 1,
0506: upperYPadIndex);
0507: for (int y = lowerYPadIndex; y < maxY; y++) {
0508: // get the index of the current row in the backend-table ...
0509: final TableCutList.CutEntry currentRowValue = yBounds
0510: .getValueAt(y);
0511: final int currentRowIndex = currentRowValue
0512: .getPosition();
0513:
0514: // for every row we iterate over all columns ...
0515: // but we do not touch the last column ..
0516: for (int x = lowerXPadIndex; x < maxX; x++) {
0517: // again get the column index for the backend table ...
0518: final TableCutList.CutEntry currentColumnValue = xBounds
0519: .getValueAt(x);
0520: final int currentColumnIndex = currentColumnValue
0521: .getPosition();
0522:
0523: objectIdTable.setObject(currentRowIndex,
0524: currentColumnIndex, cellReference);
0525: }
0526: }
0527: }
0528: }
0529: }
0530:
0531: private boolean addLine(final RenderBox element,
0532: TableCellDefinition background, final long shift,
0533: final long elementY, final long shiftedY) {
0534: // This method handles several special cases. If the element is a non-area box with borderss,
0535: // it mapps the borders into a equivalent line-definition.
0536: final long width = element.getWidth();
0537: final long height = element.getHeight();
0538: if (width == 0 && height == 0) {
0539: if (background != null) {
0540: background.setLineHint(null);
0541: if (background.getAnchor() != null) {
0542: // Elements that define anchors are an exception. We add it ..
0543: return false;
0544: }
0545: }
0546: // this element will be invisible. We do not add it ..
0547: return true;
0548: }
0549:
0550: // line definitions are treated like boxes that span from zero to the position of the
0551: // line.
0552: boolean lineVertical = false;
0553: long linePosition = 0;
0554: BorderEdge significantEdge = BorderEdge.EMPTY;
0555:
0556: final Border border = element.getBoxDefinition().getBorder();
0557: if (width == 0) {
0558: if (BorderEdge.EMPTY.equals(border.getLeft()) == false) {
0559: significantEdge = border.getLeft();
0560: lineVertical = true;
0561: linePosition = element.getX();
0562: } else if (BorderEdge.EMPTY.equals(border.getRight()) == false) {
0563: significantEdge = border.getRight();
0564: lineVertical = true;
0565: linePosition = element.getX() + element.getWidth();
0566: }
0567: }
0568:
0569: boolean lineHorizontal = false;
0570: if (height == 0) {
0571: if (BorderEdge.EMPTY.equals(border.getTop()) == false) {
0572: significantEdge = border.getTop();
0573: lineHorizontal = true;
0574: linePosition = shiftedY;
0575: } else if (BorderEdge.EMPTY.equals(border.getBottom()) == false) {
0576: significantEdge = border.getBottom();
0577: lineHorizontal = true;
0578: linePosition = shiftedY + element.getHeight();
0579: }
0580: }
0581: if (background != null) {
0582: final Line2D lineHint = background.getLineHint();
0583: if (lineHint != null) {
0584: final BorderEdge edge = ProcessUtility
0585: .produceBorderEdge(element.getStyleSheet());
0586: if (BorderEdge.EMPTY.equals(edge) == false) {
0587: if (height > 0
0588: && lineHint.getX1() == lineHint.getX2()) {
0589: lineVertical = true;
0590: if (lineHint.getX1() == 0) {
0591: linePosition = element.getX();
0592: } else {
0593: linePosition = element.getX()
0594: + element.getWidth();
0595: }
0596: }
0597: if (width > 0
0598: && lineHint.getY1() == lineHint.getY2()) {
0599: lineHorizontal = true;
0600: if (lineHint.getY1() == 0) {
0601: linePosition = shiftedY;
0602: } else {
0603: linePosition = shiftedY
0604: + element.getHeight();
0605: }
0606: }
0607: significantEdge = edge;
0608: }
0609: }
0610: }
0611:
0612: if ((lineHorizontal && lineVertical)
0613: || (lineHorizontal == false && lineVertical == false)
0614: || linePosition < 0
0615: || BorderEdge.EMPTY.equals(significantEdge)) {
0616: // an invalid definition. it will be ignored ...
0617: if (background != null && background.getLineHint() != null) {
0618: // this is a line, not a content element, and the line itself is invalid. Ignore it
0619: background.setLineHint(null);
0620: if (background.getAnchor() != null) {
0621: // Elements that define anchors are an exception. We add it ..
0622: return false;
0623: }
0624: return true;
0625: }
0626: return false;
0627: }
0628:
0629: if (background == null) {
0630: background = new TableCellDefinition(element, shift);
0631: }
0632:
0633: final long elementX = element.getX();
0634: final long elementRightX = (element.getWidth() + elementX);
0635: final long elementBottomY = element.getHeight() + shiftedY;
0636:
0637: final int lowerXIndex;
0638: final int upperXIndex;
0639: final int lowerYIndex;
0640: final int upperYIndex;
0641:
0642: // Beginn the mapping ..
0643: if (lineHorizontal) {
0644: ensureXMapping(elementX, false);
0645: ensureXMapping(elementRightX, false);
0646: lowerXIndex = xBounds.findKeyPosition(elementX,
0647: SheetLayout.LOWER_BOUNDS);
0648: upperXIndex = xBounds.findKeyPosition(elementRightX,
0649: SheetLayout.UPPER_BOUNDS);
0650:
0651: if (linePosition == 0) {
0652: ensureYMapping(elementY, false);
0653: lowerYIndex = yBounds.findKeyPosition(shiftedY,
0654: SheetLayout.LOWER_BOUNDS);
0655: upperYIndex = yBounds.findKeyPosition(
0656: elementBottomY + 1, SheetLayout.UPPER_BOUNDS);
0657: background.setTop(significantEdge);
0658: } else {
0659: ensureYMapping(elementBottomY, false);
0660: lowerYIndex = yBounds.findKeyPosition(shiftedY - 1,
0661: SheetLayout.LOWER_BOUNDS);
0662: upperYIndex = yBounds.findKeyPosition(elementBottomY,
0663: SheetLayout.UPPER_BOUNDS);
0664: background.setBottom(significantEdge);
0665: }
0666: } else // if (lineVertical)
0667: {
0668: ensureYMapping(elementY, false);
0669: ensureYMapping(elementBottomY, false);
0670: lowerYIndex = yBounds.findKeyPosition(shiftedY,
0671: SheetLayout.LOWER_BOUNDS);
0672: upperYIndex = yBounds.findKeyPosition(elementBottomY,
0673: SheetLayout.UPPER_BOUNDS);
0674:
0675: if (linePosition == 0) {
0676: ensureXMapping(elementX, false);
0677: lowerXIndex = xBounds.findKeyPosition(elementX,
0678: SheetLayout.LOWER_BOUNDS);
0679: upperXIndex = xBounds.findKeyPosition(elementX + 1,
0680: SheetLayout.UPPER_BOUNDS);
0681: background.setLeft(significantEdge);
0682: } else {
0683: ensureXMapping(elementRightX, false);
0684: lowerXIndex = xBounds.findKeyPosition(elementX - 1,
0685: SheetLayout.LOWER_BOUNDS);
0686: upperXIndex = xBounds.findKeyPosition(elementX,
0687: SheetLayout.UPPER_BOUNDS);
0688: background.setRight(significantEdge);
0689: }
0690: }
0691:
0692: // update the collected maximums
0693: if (xMaxBounds < elementRightX) {
0694: xMaxBounds = elementRightX;
0695: }
0696: if (yMaxBounds < elementBottomY) {
0697: yMaxBounds = elementBottomY;
0698: }
0699:
0700: processAreaBackground(lowerXIndex, lowerYIndex, upperXIndex,
0701: upperYIndex, background);
0702: background.setLineHint(null);
0703: return true;
0704: }
0705:
0706: private boolean isCellAreaOccupied(final int lowerXIndex,
0707: final int lowerYIndex, final int upperXIndex,
0708: final int upperYIndex, final String newContent) {
0709: if (lowerXIndex == upperXIndex || lowerYIndex == upperYIndex) {
0710: // not an area object, and therefore not valid ..
0711: return false;
0712: }
0713:
0714: final int maxX = Math.max(lowerXIndex + 1, upperXIndex);
0715: final int maxY = Math.max(lowerYIndex + 1, upperYIndex);
0716: for (int y = lowerYIndex; y < maxY; y++) {
0717: // get the index of the current row in the backend-table ...
0718: final TableCutList.CutEntry currentRowValue = yBounds
0719: .getValueAt(y);
0720: final int currentRowIndex = currentRowValue.getPosition();
0721:
0722: // for every row we iterate over all columns ...
0723: // but we do not touch the last column ..
0724: for (int x = lowerXIndex; x < maxX; x++) {
0725: // again get the column index for the backend table ...
0726: final TableCutList.CutEntry currentColumnValue = xBounds
0727: .getValueAt(x);
0728: final int currentColumnIndex = currentColumnValue
0729: .getPosition();
0730:
0731: final Object o = objectIdTable.getObject(
0732: currentRowIndex, currentColumnIndex);
0733: if (o != null) {
0734: Log.warn("Overlapping elements detected. Cell at ("
0735: + currentColumnIndex + ", "
0736: + currentRowIndex + ") is occupied by " + o
0737: + " but content from element " + newContent
0738: + " tried to use the same space.");
0739: return true;
0740: }
0741: }
0742: }
0743: return false;
0744: }
0745:
0746: private void processAreaBackground(final int lowerXIndex,
0747: final int lowerYIndex, final int upperXIndex,
0748: final int upperYIndex, final TableCellDefinition background) {
0749: // final boolean hasRightBorders = (BorderEdge.EMPTY.equals(background.getRight()) == false);
0750: // final boolean hasBottomBorders = (BorderEdge.EMPTY.equals(background.getBottom()) == false);
0751:
0752: final int maxX = Math.max(lowerXIndex + 1, upperXIndex);
0753: final int maxY = Math.max(lowerYIndex + 1, upperYIndex);
0754: for (int y = lowerYIndex; y < maxY; y++) {
0755: // get the index of the current row in the backend-table ...
0756: final TableCutList.CutEntry currentRowValue = yBounds
0757: .getValueAt(y);
0758: final int currentRowIndex = currentRowValue.getPosition();
0759:
0760: // for every row we iterate over all columns ...
0761: // but we do not touch the last column ..
0762: for (int x = lowerXIndex; x < maxX; x++) {
0763: // again get the column index for the backend table ...
0764: final TableCutList.CutEntry currentColumnValue = xBounds
0765: .getValueAt(x);
0766: final int currentColumnIndex = currentColumnValue
0767: .getPosition();
0768:
0769: workBounds = computeCellBounds(workBounds,
0770: currentColumnValue.getCoordinate(),
0771: currentRowValue.getCoordinate());
0772: performMergeCellBackground(currentRowIndex,
0773: currentColumnIndex, background, workBounds);
0774: }
0775: }
0776:
0777: // if (hasRightBorders && (xBounds.getKeyAt(upperXIndex) == xMaxBounds))
0778: // {
0779: // lastColumnCutIsSignificant = true;
0780: // }
0781: // if (hasBottomBorders && (yBounds.getKeyAt(upperYIndex) == yMaxBounds))
0782: // {
0783: // lastRowCutIsSignificant = true;
0784: // }
0785: //
0786: }
0787:
0788: /**
0789: * This method computes the cell bounds for a cell on a given gid position. If the retval parameter is non-null, the
0790: * computed cell bounds will be copied into the given object to avoid unnecessary object creation.
0791: *
0792: * @param retval the bounds, to which the computed result should be copied, or null, if a new object should be
0793: * returned.
0794: * @param xVal the x coordinates within the grid
0795: * @param yVal the y coordinates within the grid
0796: * @return the computed cell bounds.
0797: */
0798: private StrictBounds computeCellBounds(final StrictBounds retval,
0799: final long xVal, final long yVal) {
0800: final long x2Val = xBounds.findKey(xVal + 1,
0801: SheetLayout.UPPER_BOUNDS);
0802: final long y2Val = yBounds.findKey(yVal + 1,
0803: SheetLayout.UPPER_BOUNDS);
0804: if (retval == null) {
0805: return new StrictBounds(xVal, yVal, x2Val - xVal, y2Val
0806: - yVal);
0807: }
0808: retval.setRect(xVal, yVal, x2Val - xVal, y2Val - yVal);
0809: return retval;
0810: }
0811:
0812: private void performMergeCellBackground(final int currentRowIndex,
0813: final int currentColumnIndex,
0814: final TableCellDefinition background,
0815: final StrictBounds bounds) {
0816: // get the old background ... we will merge this one with the new ..
0817: final TableCellDefinition oldBackground = (TableCellDefinition) backend
0818: .getObject(currentRowIndex, currentColumnIndex);
0819: final TableCellDefinition newBackground;
0820: if (oldBackground == null) {
0821: // split the element ..
0822: newBackground = background.normalize(bounds);
0823: } else {
0824: newBackground = oldBackground.merge(background, bounds);
0825: }
0826: backend.setObject(currentRowIndex, currentColumnIndex,
0827: newBackground);
0828: }
0829:
0830: private void ensureXMapping(final long coordinate, final boolean aux) {
0831: final TableCutList.CutEntry cut = xBounds.get(coordinate);
0832: if (cut == null) {
0833: final int result = xBounds.size();
0834: xBounds.put(coordinate, new TableCutList.CutEntry(
0835: coordinate, result, aux));
0836:
0837: // backend copy ...
0838: final int oldColumn = getPreviousColumnPosition(coordinate);
0839: if (coordinate < xMaxBounds) {
0840: columnInserted(coordinate, oldColumn, result);
0841: }
0842: } else if (cut.isAuxilary() && aux == false) {
0843: cut.makePermanent();
0844: }
0845: }
0846:
0847: /**
0848: * Splits the background column into two new columns.
0849: *
0850: * @param coordinate
0851: * @param oldColumn
0852: * @param newColumn
0853: */
0854: protected void columnInserted(final long coordinate,
0855: final int oldColumn, final int newColumn) {
0856: // Log.debug("Inserting new column on position " + coordinate +
0857: // " (Col: " + oldColumn + " -> " + newColumn);
0858: //
0859: // now copy all entries from old column to new column
0860: backend.copyColumn(oldColumn, newColumn);
0861: objectIdTable.copyColumn(oldColumn, newColumn);
0862:
0863: // handle the backgrounds ..
0864: StrictBounds rightBounds = null;
0865: final TableCutList.CutEntry[] entries = yBounds.getRawEntries();
0866: final int size = yBounds.size();
0867: for (int i = 0; i < size; i++) {
0868: final TableCutList.CutEntry bcut = entries[i];
0869:
0870: final int position = bcut.getPosition();
0871: final TableCellDefinition originalBackground = (TableCellDefinition) backend
0872: .getObject(position, newColumn);
0873: if (originalBackground == null) {
0874: continue;
0875: }
0876:
0877: // a column has been inserted. We have to check, whether the background has
0878: // borders defined, which might be invalid now.
0879: rightBounds = computeCellBounds(rightBounds, coordinate,
0880: bcut.getCoordinate());
0881:
0882: // the bounds of the old background have to be adjusted too ..
0883: final StrictBounds leftBounds = originalBackground
0884: .getBounds();
0885: final long parentNewWidth = rightBounds.getX()
0886: - leftBounds.getX();
0887: leftBounds
0888: .setRect(leftBounds.getX(), leftBounds.getY(), Math
0889: .max(0, parentNewWidth), leftBounds
0890: .getHeight());
0891: // the original cell was split into two new cells ...
0892: // the new right border is no longer filled ...
0893: // a border was found, but is invalid now.
0894:
0895: final TableCellDefinition rightBackground = originalBackground
0896: .normalize(rightBounds);
0897: final TableCellDefinition leftBackground = originalBackground
0898: .normalize(leftBounds);
0899:
0900: backend.setObject(position, oldColumn, leftBackground);
0901: backend.setObject(position, newColumn, rightBackground);
0902: }
0903: }
0904:
0905: private int getPreviousColumnPosition(final long coordinate) {
0906: final TableCutList.CutEntry entry = xBounds
0907: .getPrevious(coordinate);
0908: if (entry == null) {
0909: return -1;
0910: }
0911: return entry.getPosition();
0912: }
0913:
0914: protected void rowInserted(final long coordinate, final int oldRow,
0915: final int newRow) {
0916: if (oldRow < 0) {
0917: throw new IndexOutOfBoundsException(
0918: "OldRow cannot be negative: " + coordinate);
0919: }
0920: if (newRow < 0) {
0921: throw new IndexOutOfBoundsException(
0922: "NewRow cannot be negative: " + coordinate);
0923: }
0924: // Log.debug("Inserting new row on position " + coordinate +
0925: // " (Row: " + oldRow + " -> " + newRow);
0926:
0927: // now copy all entries from old column to new column
0928: backend.copyRow(oldRow, newRow);
0929: objectIdTable.copyRow(oldRow, newRow);
0930:
0931: // handle the backgrounds ..
0932: StrictBounds cellBounds = null;
0933:
0934: final TableCutList.CutEntry[] entries = xBounds.getRawEntries();
0935: final int xEntrySize = xBounds.size();
0936: for (int i = 0; i < xEntrySize; i++) {
0937: final TableCutList.CutEntry bcut = entries[i];
0938:
0939: final int position = bcut.getPosition();
0940: final TableCellDefinition originalBackground = (TableCellDefinition) backend
0941: .getObject(newRow, position);
0942: if (originalBackground == null) {
0943: continue;
0944: }
0945:
0946: // a row has been inserted. We have to check, whether the background has
0947: // borders defined, which might be invalid now.
0948: cellBounds = computeCellBounds(cellBounds, bcut
0949: .getCoordinate(), coordinate);
0950:
0951: // the bounds of the old background have to be adjusted too ..
0952: final StrictBounds bounds = originalBackground.getBounds();
0953: final long parentNewHeight = cellBounds.getY()
0954: - bounds.getY();
0955: bounds.setRect(bounds.getX(), bounds.getY(), bounds
0956: .getWidth(), Math.max(0, parentNewHeight));
0957: // due to the merging it is possible, that the bottom border
0958: // is invalid now.
0959: // the Top-Border of the original background is not touched ...
0960:
0961: final TableCellDefinition bottomBackground = originalBackground
0962: .normalize(cellBounds);
0963: final TableCellDefinition topBackground = originalBackground
0964: .normalize(bounds);
0965: backend.setObject(oldRow, position, topBackground);
0966: backend.setObject(newRow, position, bottomBackground);
0967: }
0968: }
0969:
0970: private int getPreviousRowPosition(final long coordinate) {
0971: final TableCutList.CutEntry entry = yBounds
0972: .getPrevious(coordinate);
0973: if (entry == null) {
0974: return -1;
0975: }
0976: // Log.debug ("GetPreviousRow: " + entry.getCoordinate() + " (prev to " + coordinate + ")");
0977: if (entry.getCoordinate() >= coordinate) {
0978: throw new IllegalStateException(
0979: "GetPrevious returned an invalid result:"
0980: + entry.getCoordinate() + " (prev to "
0981: + coordinate + ')');
0982: }
0983: return entry.getPosition();
0984: }
0985:
0986: private void ensureYMapping(final long coordinate, final boolean aux) {
0987: final TableCutList.CutEntry cut = yBounds.get(coordinate);
0988: if (cut == null) {
0989: final int result = yBounds.size();
0990: yBounds.put(coordinate, new TableCutList.CutEntry(
0991: coordinate, result, aux));
0992:
0993: final int oldRow = getPreviousRowPosition(coordinate);
0994: if (coordinate < yMaxBounds) {
0995: // oh, an insert operation. Make sure that everyone updates its state.
0996: rowInserted(coordinate, oldRow, result);
0997: }
0998: } else if (cut.isAuxilary() && aux == false) {
0999: cut.makePermanent();
1000: }
1001: }
1002:
1003: /**
1004: * Gets the strict mode flag.
1005: *
1006: * @return true, if strict mode is enabled, false otherwise.
1007: */
1008: public boolean isStrict() {
1009: return strict;
1010: }
1011:
1012: protected GenericObjectTable getLayoutBackend() {
1013: return backend;
1014: }
1015:
1016: public boolean isEmpty() {
1017: return ((backend.getColumnCount() == 0)
1018: && (backend.getRowCount() == 0) && xMaxBounds == 0 && yMaxBounds == 0);
1019: }
1020:
1021: /**
1022: * Returns the position of the given element within the table. The TableRectangle contains row and cell indices, no
1023: * layout coordinates.
1024: *
1025: * @param x the element bounds for which the table bounds should be found.
1026: * @param y the element bounds for which the table bounds should be found.
1027: * @param width the element bounds for which the table bounds should be found.
1028: * @param height the element bounds for which the table bounds should be found.
1029: * @param rect the returned rectangle or null, if a new instance should be created
1030: * @return the filled table rectangle.
1031: */
1032: public TableRectangle getTableBounds(final long x, final long y,
1033: final long width, final long height, TableRectangle rect) {
1034: if (rect == null) {
1035: rect = new TableRectangle();
1036: }
1037: final int x1 = xBounds.findKeyPosition(x,
1038: SheetLayout.LOWER_BOUNDS);
1039: final int y1 = yBounds.findKeyPosition(y,
1040: SheetLayout.LOWER_BOUNDS);
1041: final int x2 = xBounds.findKeyPosition(x + width,
1042: SheetLayout.UPPER_BOUNDS);
1043: final int y2 = yBounds.findKeyPosition(y + height,
1044: SheetLayout.UPPER_BOUNDS);
1045: rect.setRect(x1, y1, x2, y2);
1046: return rect;
1047: }
1048:
1049: /**
1050: * Returns the position of the given element within the table. The TableRectangle contains row and cell indices, no
1051: * layout coordinates.
1052: *
1053: * @param bounds the element bounds for which the table bounds should be found.
1054: * @param rect the returned rectangle or null, if a new instance should be created
1055: * @return the filled table rectangle.
1056: */
1057: public TableRectangle getTableBounds(final StrictBounds bounds,
1058: TableRectangle rect) {
1059: if (rect == null) {
1060: rect = new TableRectangle();
1061: }
1062: final int x1 = xBounds.findKeyPosition(bounds.getX(),
1063: SheetLayout.LOWER_BOUNDS);
1064: final int y1 = yBounds.findKeyPosition(bounds.getY(),
1065: SheetLayout.LOWER_BOUNDS);
1066: final int x2 = xBounds.findKeyPosition(bounds.getX()
1067: + bounds.getWidth(), SheetLayout.UPPER_BOUNDS);
1068: final int y2 = yBounds.findKeyPosition(bounds.getY()
1069: + bounds.getHeight(), SheetLayout.UPPER_BOUNDS);
1070: rect.setRect(x1, y1, x2, y2);
1071: return rect;
1072: }
1073:
1074: protected int mapColumn(final int xCutIndex) {
1075: final TableCutList.CutEntry boundsCut = xBounds
1076: .getValueAt(xCutIndex);
1077: if (boundsCut == null) {
1078: throw new IllegalStateException("There is no column at "
1079: + xCutIndex);
1080: }
1081: return boundsCut.getPosition();
1082: }
1083:
1084: protected int mapRow(final int yCutIndex) {
1085: final TableCutList.CutEntry boundsCut = yBounds
1086: .getValueAt(yCutIndex);
1087: if (boundsCut == null) {
1088: throw new IllegalStateException("There is no row at "
1089: + yCutIndex);
1090: }
1091: return boundsCut.getPosition();
1092: }
1093:
1094: /**
1095: * A Callback method to inform the sheet layout, that the current page is complete, and no more content will be
1096: * added.
1097: */
1098: public void pageCompleted() {
1099: removeAuxilaryBounds();
1100: clearObjectIdTable();
1101: }
1102:
1103: protected void removeAuxilaryBounds() {
1104: ensureXMapping(this .xMaxBounds, false);
1105: ensureYMapping(this .yMaxBounds, false);
1106: // Log.debug("Size: " + getRowCount() + ", " + getColumnCount());
1107:
1108: final ArrayList removedCuts = new ArrayList();
1109: final TableCutList.CutEntry[] xEntries = (TableCutList.CutEntry[]) xBounds
1110: .getRawEntries().clone();
1111: final int xEntrySize = xBounds.size();
1112: for (int i = xEntrySize - 1; i >= 0; i--) {
1113: final TableCutList.CutEntry cut = xEntries[i];
1114: if (cut.isAuxilary()) {
1115: xBounds.remove(cut.getCoordinate());
1116: removedCuts.add(cut);
1117: }
1118: }
1119:
1120: // now join the cuts with their left neighbour ..
1121: for (int i = 0; i < removedCuts.size(); i++) {
1122: // the col-cut that will be removed/merged/whatever ...
1123: final TableCutList.CutEntry removedCut = (TableCutList.CutEntry) removedCuts
1124: .get(i);
1125: final int removedColPosition = removedCut.getPosition();
1126: // the col cut marking the cell that will receive the merged content.
1127: final TableCutList.CutEntry prevCut = xBounds
1128: .getPrevious(removedCut.getCoordinate());
1129: final int previousColPosition = prevCut.getPosition();
1130:
1131: for (int row = 0; row < getRowCount(); row++) {
1132: final int mappedRow = mapRow(row);
1133: final TableCellDefinition leftBg = (TableCellDefinition) backend
1134: .getObject(mappedRow, previousColPosition);
1135: final TableCellDefinition rightBg = (TableCellDefinition) backend
1136: .getObject(mappedRow, removedColPosition);
1137: if (leftBg == null && rightBg == null) {
1138: continue;
1139: }
1140: if (leftBg == null) {
1141: final long x = prevCut.getCoordinate();
1142: final long y = getYPosition(row);
1143: final StrictBounds bounds = computeCellBounds(null,
1144: x, y);
1145: final TableCellDefinition unionBg = rightBg
1146: .normalize(bounds);
1147: backend.setObject(mappedRow, previousColPosition,
1148: unionBg);
1149: backend.setObject(mappedRow, removedColPosition,
1150: null);
1151: } else if (rightBg == null) {
1152: final long x = prevCut.getCoordinate();
1153: final long y = getYPosition(row);
1154: final StrictBounds bounds = computeCellBounds(null,
1155: x, y);
1156: final TableCellDefinition unionBg = leftBg
1157: .normalize(bounds);
1158: backend.setObject(mappedRow, previousColPosition,
1159: unionBg);
1160: backend.setObject(mappedRow, removedColPosition,
1161: null);
1162: } else {
1163: // now join ..
1164: final StrictBounds leftBounds = leftBg.getBounds();
1165: final StrictBounds newBounds = rightBg.getBounds()
1166: .createUnion(leftBounds);
1167: final TableCellDefinition unionBg = leftBg
1168: .normalize(newBounds);
1169: if (unionBg != null) {
1170: unionBg.setRight(rightBg.getRight());
1171: }
1172:
1173: backend.setObject(mappedRow, previousColPosition,
1174: unionBg);
1175: backend.setObject(mappedRow, removedColPosition,
1176: null);
1177: }
1178: }
1179: }
1180: removedCuts.clear();
1181:
1182: final TableCutList.CutEntry[] yEntries = (TableCutList.CutEntry[]) yBounds
1183: .getRawEntries().clone();
1184: final int ySize = yBounds.size();
1185: for (int i = 0; i < ySize; i++) {
1186: final TableCutList.CutEntry cut = yEntries[i];
1187: if (cut.isAuxilary()) {
1188: removedCuts.add(cut);
1189: }
1190: }
1191:
1192: yBounds.removeAll(removedCuts);
1193:
1194: // now join the cuts with their top neighbour ..
1195: for (int i = 0; i < removedCuts.size(); i++) {
1196: // the row-cut that will be removed/merged/whatever ...
1197: final TableCutList.CutEntry removedCut = (TableCutList.CutEntry) removedCuts
1198: .get(i);
1199: final int rowPosition = removedCut.getPosition();
1200: // the row cut marking the cell that will receive the merged content.
1201: final TableCutList.CutEntry prevCut = yBounds
1202: .getPrevious(removedCut.getCoordinate());
1203: final int previousRowPosition = prevCut.getPosition();
1204:
1205: for (int col = 0; col < getColumnCount(); col++) {
1206: final int mappedColumn = mapColumn(col);
1207: final TableCellDefinition topBg = (TableCellDefinition) backend
1208: .getObject(previousRowPosition, mappedColumn);
1209: final TableCellDefinition bottomBg = (TableCellDefinition) backend
1210: .getObject(rowPosition, mappedColumn);
1211: if (topBg == null && bottomBg == null) {
1212: // do nothing ...
1213: } else if (topBg == null) {
1214: // the cut has been removed already, so that the coordinate given in the boundsCut is
1215: // now invalid. It would point to a different location now.
1216: // however, the x-positions are still valid.
1217:
1218: final long x = getXPosition(col);
1219: final long y = prevCut.getCoordinate();
1220: final StrictBounds bounds = computeCellBounds(null,
1221: x, y);
1222: final TableCellDefinition unionBg = bottomBg
1223: .normalize(bounds);
1224: backend.setObject(previousRowPosition,
1225: mappedColumn, unionBg);
1226: backend.setObject(rowPosition, mappedColumn, null);
1227: } else if (bottomBg == null) {
1228: final long x = getXPosition(col);
1229: final long y = prevCut.getCoordinate();
1230: final StrictBounds bounds = computeCellBounds(null,
1231: x, y);
1232: final TableCellDefinition unionBg = topBg
1233: .normalize(bounds);
1234: backend.setObject(previousRowPosition,
1235: mappedColumn, unionBg);
1236: backend.setObject(rowPosition, mappedColumn, null);
1237: } else {
1238: // now join ..
1239: final StrictBounds topBounds = topBg.getBounds();
1240: final StrictBounds newBounds = bottomBg.getBounds()
1241: .createUnion(topBounds);
1242: final TableCellDefinition unionBg = topBg
1243: .normalize(newBounds);
1244: if (unionBg != null) {
1245: unionBg.setBottom(bottomBg.getBottom());
1246: }
1247: backend.setObject(previousRowPosition,
1248: mappedColumn, unionBg);
1249: backend.setObject(rowPosition, mappedColumn, null);
1250: }
1251: }
1252: }
1253: }
1254:
1255: protected void clearObjectIdTable() {
1256: objectIdTable.clear();
1257: objectIdTable.ensureCapacity(backend.getRowCount(), backend
1258: .getColumnCount());
1259: }
1260:
1261: /**
1262: * Returns the element at grid-position (x,y). This returns the cell background for a certain cell, or null, if there
1263: * is no background at that cell.
1264: *
1265: * @param row the row of the requested element
1266: * @param column the column starting with zero.
1267: * @return the element at the specified position.
1268: */
1269: public TableCellDefinition getBackgroundAt(final int row,
1270: final int column) {
1271: final int mappedRow = mapRow(row);
1272: final int mappedColumn = mapColumn(column);
1273: return (TableCellDefinition) backend.getObject(mappedRow,
1274: mappedColumn);
1275: }
1276:
1277: /**
1278: * Computes the height of the given row.
1279: *
1280: * @param row the row, for which the height should be computed.
1281: * @return the height of the row.
1282: * @throws IndexOutOfBoundsException if the row is invalid.
1283: */
1284: public long getRowHeight(final int row) {
1285: final int rowCount = yBounds.size();
1286: if (row >= rowCount) {
1287: throw new IndexOutOfBoundsException("Row " + row
1288: + " is invalid. Max valid row is " + (rowCount - 1));
1289: }
1290:
1291: final long bottomBorder;
1292: if ((row + 1) < rowCount) {
1293: bottomBorder = yBounds.getKeyAt(row + 1);
1294: } else {
1295: bottomBorder = yMaxBounds;
1296: }
1297:
1298: return bottomBorder - yBounds.getKeyAt(row);
1299: }
1300:
1301: public long getMaxHeight() {
1302: return yMaxBounds;
1303: }
1304:
1305: public long getMaxWidth() {
1306: return xMaxBounds;
1307: }
1308:
1309: public long getCellWidth(final int startCell) {
1310: return getCellWidth(startCell, startCell + 1);
1311: }
1312:
1313: /**
1314: * Computes the height of the given row.
1315: *
1316: * @param startCell the first cell in the range
1317: * @param endCell the last cell included in the cell range
1318: * @return the height of the row.
1319: * @throws IndexOutOfBoundsException if the row is invalid.
1320: */
1321: public long getCellWidth(final int startCell, final int endCell) {
1322: if (startCell < 0) {
1323: throw new IndexOutOfBoundsException(
1324: "Start-Cell must not be negative");
1325: }
1326: if (endCell < 0) {
1327: throw new IndexOutOfBoundsException(
1328: "End-Cell must not be negative");
1329: }
1330: if (endCell < startCell) {
1331: throw new IndexOutOfBoundsException(
1332: "End-Cell must not smaller than end-cell");
1333: }
1334:
1335: final long rightBorder;
1336: if (endCell >= xBounds.size()) {
1337: rightBorder = xMaxBounds;
1338: } else {
1339: rightBorder = xBounds.getKeyAt(endCell);
1340: }
1341: return rightBorder - xBounds.getKeyAt(startCell);
1342: }
1343:
1344: public long getRowHeight(final int startRow, final int endRow) {
1345: if (startRow < 0) {
1346: throw new IndexOutOfBoundsException(
1347: "Start-Cell must not be negative");
1348: }
1349: if (endRow < 0) {
1350: throw new IndexOutOfBoundsException(
1351: "End-Cell must not be negative");
1352: }
1353: if (endRow < startRow) {
1354: throw new IndexOutOfBoundsException(
1355: "End-Cell must not smaller than end-cell");
1356: }
1357:
1358: final long bottomBorder;
1359: if (endRow >= yBounds.size()) {
1360: bottomBorder = yMaxBounds;
1361: } else {
1362: bottomBorder = yBounds.getKeyAt(endRow);
1363: }
1364: return bottomBorder - yBounds.getKeyAt(startRow);
1365: }
1366:
1367: /**
1368: * The current number of columns. Of course, this value begins to be reliable, once the number of columns is known
1369: * (that is at the end of the layouting process).
1370: *
1371: * @return the number columns.
1372: */
1373: public int getColumnCount() {
1374: return Math.max(xBounds.size() - 1, 0);
1375: }
1376:
1377: /**
1378: * The current number of rows. Of course, this value begins to be reliable, once the number of rows is known (that is
1379: * at the end of the layouting process).
1380: *
1381: * @return the number columns.
1382: */
1383: public int getRowCount() {
1384: return Math.max(yBounds.size() - 1, 0);
1385: }
1386:
1387: public long getXPosition(final int col) {
1388: return xBounds.getKeyAt(col);
1389: }
1390:
1391: public long getYPosition(final int row) {
1392: return yBounds.getKeyAt(row);
1393: }
1394:
1395: public TableCellDefinition getBackgroundAt(final int x,
1396: final int y, final int columnSpan, final int rowSpan) {
1397: if (rowSpan == 1 && columnSpan == 1) {
1398: return getBackgroundAt(x, y);
1399: }
1400:
1401: // make a large-scale merge ...
1402: final StrictBounds bounds = new StrictBounds();
1403: // First merge all cells on each row. This is a merge in horizontal direction, much like we did on end-Sheet.
1404: // This way we get a list of 1-col-cells per row, that can then be merged vertically.
1405: final TableCellDefinition[] rowBackgrounds = new TableCellDefinition[rowSpan];
1406: for (int row = 0; row < rowSpan; row += 1) {
1407: for (int col = 0; col < columnSpan; col += 1) {
1408: final TableCellDefinition rightBackground = getBackgroundAt(
1409: y + row, x + col);
1410: if (rightBackground == null) {
1411: continue;
1412: }
1413: if (rowBackgrounds[row] == null) {
1414: rowBackgrounds[row] = rightBackground;
1415: } else {
1416: // merge.
1417: final TableCellDefinition leftBackground = rowBackgrounds[row];
1418: bounds.setRect(leftBackground.getX(),
1419: leftBackground.getY(), rightBackground
1420: .getX()
1421: + rightBackground.getWidth()
1422: - leftBackground.getX(),
1423: leftBackground.getHeight());
1424: rowBackgrounds[row] = leftBackground.merge(
1425: rightBackground, bounds);
1426: }
1427:
1428: }
1429: }
1430:
1431: TableCellDefinition topBackground = rowBackgrounds[0];
1432: for (int i = 1; i < rowBackgrounds.length; i++) {
1433: final TableCellDefinition bottomBackground = rowBackgrounds[i];
1434: if (bottomBackground == null) {
1435: continue;
1436: }
1437: if (topBackground == null) {
1438: topBackground = bottomBackground;
1439: continue;
1440: }
1441: bounds.setRect(topBackground.getX(), topBackground.getY(),
1442: topBackground.getWidth(), bottomBackground.getY()
1443: + bottomBackground.getHeight()
1444: - topBackground.getY());
1445: topBackground = topBackground.merge(bottomBackground,
1446: bounds);
1447: }
1448: // normalize before returning ..
1449: bounds.setRect(getXPosition(x), getYPosition(y), getCellWidth(
1450: x, x + columnSpan), getRowHeight(y, y + rowSpan));
1451: return topBackground.normalize(bounds);
1452: }
1453: }
|