package com.vaadin.addon.spreadsheet.client; /* * #%L * Vaadin Spreadsheet * %% * Copyright (C) 2013 - 2015 Vaadin Ltd * %% * This program is available under Commercial Vaadin Add-On License 3.0 * (CVALv3). * * See the file license.html distributed with this software for more * information about licensing. * * You should have received a copy of the CVALv3 along with this program. * If not, see <http://vaadin.com/license/cval-3>. * #L% */ import java.util.Objects; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Overflow; public class Cell { public static final String CELL_COMMENT_TRIANGLE_CLASSNAME = "cell-comment-triangle"; public static final String CELL_INVALID_FORMULA_CLASSNAME = "cell-invalidformula-triangle"; private static final int ZINDEXVALUE = 2; private final DivElement element; private DivElement cellCommentTriangle; private DivElement invalidFormulaTriangle; /** * 1-based */ private int col; /** * 1-based */ private int row; private Element popupButtonElement; private String value; private String cellStyle = "cs0"; private boolean needsMeasure; private SheetWidget sheetWidget; private boolean overflowDirty = true; private boolean overflowing; public Cell(SheetWidget sheetWidget, int col, int row) { this.sheetWidget = sheetWidget; this.col = col; this.row = row; element = Document.get().createDivElement(); updateCellValues(); } public Cell(SheetWidget sheetWidget, int col, int row, CellData cellData) { this.sheetWidget = sheetWidget; this.col = col; this.row = row; element = Document.get().createDivElement(); if (cellData == null) { value = null; } else { needsMeasure = cellData.needsMeasure; value = cellData.value; cellStyle = cellData.cellStyle; } updateCellValues(); updateInnerText(); } public DivElement getElement() { return element; } public void update(int col, int row, CellData cellData) { this.col = col; this.row = row; cellStyle = cellData == null ? "cs0" : cellData.cellStyle; value = cellData == null ? null : cellData.value; updateInnerText(); updateCellValues(); markAsOverflowDirty(); } private void updateInnerText() { element.getStyle().setOverflow(Overflow.HIDDEN); if (value == null || value.isEmpty()) { element.setInnerText(""); element.getStyle().clearZIndex(); } else { element.getStyle().setZIndex(ZINDEXVALUE); if (needsMeasure && sheetWidget.measureValueWidth(cellStyle, value) > getElement() .getClientWidth()) { element.setInnerText("###"); } else if (value.startsWith("'")) { element.setInnerText(value.substring(1, value.length())); } else { element.setInnerText(value); } } } void updateOverflow() { boolean rightAligned = element.getAttribute("class").contains(" r ") || element.getAttribute("class").endsWith(" r"); int columnWidth = sheetWidget.actionHandler.getColWidth(col); Integer scrollW = sheetWidget.scrollWidthCache.get(getUniqueKey()); if (scrollW == null) { scrollW = measureOverflow(); } int overflowPx = scrollW - columnWidth; if (!rightAligned && overflowPx > 0) { // Increase overflow by cell left padding (2px) overflowPx += 2; int colIndex = col; int width = 0; int[] colW = sheetWidget.actionHandler.getColWidths(); boolean inFreezePane = col <= sheetWidget.verticalSplitPosition; while (colIndex < colW.length && width < overflowPx) { if (inFreezePane && colIndex >= sheetWidget.verticalSplitPosition) { break; } Cell nextCell = sheetWidget.getCell(colIndex + 1, row); if (nextCell != null && nextCell.hasValue()) { break; } width += colW[colIndex]; colIndex++; } // columnWidth is added after calculating the overflowing width width += columnWidth; // create element to contain the text, so we can apply overflow // rules DivElement overflowDiv = Document.get().createDivElement(); overflowDiv.getStyle().setProperty("pointerEvents", "none"); overflowDiv.getStyle().setWidth(width, Style.Unit.PX); overflowDiv.getStyle().setOverflow(Overflow.HIDDEN); overflowDiv.getStyle().setTextOverflow(Style.TextOverflow.ELLIPSIS); overflowDiv.setInnerText(element.getInnerText()); element.setInnerText(null); element.appendChild(overflowDiv); overflowing = true; } else { overflowing = false; } if (sheetWidget.isMergedCell(SheetWidget.toKey(col, row)) && !(this instanceof MergedCell)) { element.getStyle().setOverflow(Overflow.HIDDEN); } else { element.getStyle().setOverflow(Overflow.VISIBLE); } overflowDirty = false; } int measureOverflow() { if (overflowing) { updateInnerText(); } Integer scrollW = sheetWidget.scrollWidthCache.get(getUniqueKey()); if (scrollW == null) { scrollW = element.getScrollWidth(); sheetWidget.scrollWidthCache.put(getUniqueKey(), scrollW); } return scrollW; } protected void updateCellValues() { removeCellCommentMark(); removePopupButton(); updateClassName(); } protected void updateClassName() { element.setClassName(SheetWidget.toKey(col, row) + " cell " + cellStyle); } public String getCellStyle() { return cellStyle; } public int getCol() { return col; } public int getRow() { return row; } public String getValue() { return value; } public void setValue(String value, String cellStyle, boolean needsMeasure) { if (!this.cellStyle.equals(cellStyle)) { this.cellStyle = cellStyle; updateClassName(); } this.needsMeasure = needsMeasure; setValue(value); } public void setValue(String value) { this.value = value; updateInnerText(); if (cellCommentTriangle != null) { element.appendChild(cellCommentTriangle); } if (invalidFormulaTriangle != null) { element.appendChild(invalidFormulaTriangle); } if (popupButtonElement != null) { element.appendChild(popupButtonElement); } markAsOverflowDirty(); } public void showPopupButton(Element popupButtonElement) { this.popupButtonElement = popupButtonElement; element.appendChild(popupButtonElement); } public void removePopupButton() { if (popupButtonElement != null) { popupButtonElement.removeFromParent(); popupButtonElement = null; } } public void showCellCommentMark() { if (cellCommentTriangle == null) { cellCommentTriangle = Document.get().createDivElement(); cellCommentTriangle.setClassName(CELL_COMMENT_TRIANGLE_CLASSNAME); element.appendChild(cellCommentTriangle); } } public void removeCellCommentMark() { if (cellCommentTriangle != null) { cellCommentTriangle.removeFromParent(); cellCommentTriangle = null; } } public void showInvalidFormulaIndicator() { if (invalidFormulaTriangle == null) { invalidFormulaTriangle = Document.get().createDivElement(); invalidFormulaTriangle.setClassName(CELL_INVALID_FORMULA_CLASSNAME); element.appendChild(invalidFormulaTriangle); } } public void removeInvalidFormulaIndicator() { if (invalidFormulaTriangle != null) { invalidFormulaTriangle.removeFromParent(); invalidFormulaTriangle = null; } } public boolean isNeedsMeasure() { return needsMeasure; } /** * @param sizes * @param beginIndex * 1-based inclusive * @param endIndex * 1-based exclusive * @return */ public int countSum(int[] sizes, int beginIndex, int endIndex) { if (sizes == null || sizes.length < endIndex - 1) { return 0; } int pos = 0; for (int i = beginIndex; i < endIndex; i++) { pos += sizes[i - 1]; } return pos; } public boolean isOverflowDirty() { return value != null && !value.isEmpty() && overflowDirty; } public void markAsOverflowDirty() { overflowDirty = true; } public boolean hasValue() { return value != null && !value.isEmpty(); } private CellValueStyleKey getUniqueKey() { return new CellValueStyleKey(value, cellStyle); } static class CellValueStyleKey { private String value; private String cellStyle; public CellValueStyleKey(String value, String cellStyle) { super(); this.value = value; this.cellStyle = cellStyle; } @Override public int hashCode() { return Objects.hash(value, cellStyle); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof CellValueStyleKey)) { return false; } CellValueStyleKey other = (CellValueStyleKey) obj; return Objects.equals(value, other.value) && Objects.equals(cellStyle, other.cellStyle); } } }