blob: 62a4a276214a1a24d8180841b84c25c03ff5e5ff [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 2021 Original NatTable authors and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Original NatTable authors and others - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.waltable.painter.cell;
import static org.eclipse.statet.ecommons.waltable.coordinate.Orientation.VERTICAL;
import static org.eclipse.statet.ecommons.waltable.painter.cell.GraphicsUtils.safe;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.statet.ecommons.waltable.config.IConfigRegistry;
import org.eclipse.statet.ecommons.waltable.coordinate.LRectangle;
import org.eclipse.statet.ecommons.waltable.layer.ILayer;
import org.eclipse.statet.ecommons.waltable.layer.cell.ILayerCell;
import org.eclipse.statet.ecommons.waltable.resize.DimPositionResizeCommand;
import org.eclipse.statet.ecommons.waltable.style.CellStyleUtil;
import org.eclipse.statet.ecommons.waltable.style.IStyle;
/**
* TextPainter that draws text into a cell horizontally.
* Can handle word wrapping and/or word cutting and/or automatic calculation and resizing of the
* cell width and height if the text does not fit into the cell.
*/
public class TextPainter extends AbstractTextPainter {
public TextPainter() {
this(false, true);
}
/**
* @param wrapText split text over multiple lines
* @param paintBg skips painting the background if is FALSE
*/
public TextPainter(final boolean wrapText, final boolean paintBg) {
this(wrapText, paintBg, 0);
}
/**
* @param wrapText split text over multiple lines
* @param paintBg skips painting the background if is FALSE
* @param spacing The space between text and cell border
*/
public TextPainter(final boolean wrapText, final boolean paintBg, final int spacing) {
this(wrapText, paintBg, spacing, false);
}
/**
* @param wrapText split text over multiple lines
* @param paintBg skips painting the background if is FALSE
* @param calculate tells the text painter to calculate the cell borders regarding the content
*/
public TextPainter(final boolean wrapText, final boolean paintBg, final boolean calculate) {
this(wrapText, paintBg, 0, calculate);
}
/**
* @param wrapText split text over multiple lines
* @param paintBg skips painting the background if is FALSE
* @param spacing The space between text and cell border
* @param calculate tells the text painter to calculate the cell borders regarding the content
*/
public TextPainter(final boolean wrapText, final boolean paintBg, final int spacing, final boolean calculate) {
super(wrapText, paintBg, spacing, calculate);
}
// /**
// * @param wrapText split text over multiple lines
// * @param paintBg skips painting the background if is FALSE
// * @param spacing The space between text and cell border
// * @param calculateByTextLength tells the text painter to calculate the cell border by containing
// * text length. For horizontal text rendering, this means the width of the cell is calculated
// * by content, for vertical text rendering the height is calculated
// * @param calculateByTextHeight tells the text painter to calculate the cell border by containing
// * text height. For horizontal text rendering, this means the height of the cell is calculated
// * by content, for vertical text rendering the width is calculated
// */
// public TextPainter(boolean wrapText, boolean paintBg, long spacing,
// boolean calculateByTextLength, boolean calculateByTextHeight) {
// super(wrapText, paintBg, spacing, calculateByTextLength, calculateByTextHeight);
// }
@Override
public long getPreferredWidth(final ILayerCell cell, final GC gc, final IConfigRegistry configRegistry){
setupGCFromConfig(gc, CellStyleUtil.getCellStyle(cell, configRegistry));
return getWidthFromCache(gc, convertDataType(cell, configRegistry)) + (this.spacing*2) + 1;
}
@Override
public long getPreferredHeight(final ILayerCell cell, final GC gc, final IConfigRegistry configRegistry) {
setupGCFromConfig(gc, CellStyleUtil.getCellStyle(cell, configRegistry));
return gc.textExtent(convertDataType(cell, configRegistry)).y + (this.spacing*2) + 1;
}
@Override
public void paintCell(final ILayerCell cell, final GC gc, final LRectangle lRectangle, final IConfigRegistry configRegistry) {
if (this.paintBg) {
super.paintCell(cell, gc, lRectangle, configRegistry);
}
if (this.paintFg) {
final org.eclipse.swt.graphics.Rectangle originalClipping= gc.getClipping();
gc.setClipping(safe(lRectangle).intersection(originalClipping));
final IStyle cellStyle= CellStyleUtil.getCellStyle(cell, configRegistry);
setupGCFromConfig(gc, cellStyle);
final boolean underline= renderUnderlined(cellStyle);
final boolean strikethrough= renderStrikethrough(cellStyle);
final int fontHeight= gc.getFontMetrics().getHeight();
String text= convertDataType(cell, configRegistry);
// Draw Text
text= getTextToDisplay(cell, gc, lRectangle.width, text);
final int numberOfNewLines= getNumberOfNewLines(text);
//if the content height is bigger than the available row height
//we're extending the row height (only if word wrapping is enabled)
final int contentHeight= (fontHeight * numberOfNewLines) + (this.spacing*2);
final long contentToCellDiff= (cell.getBounds().height - lRectangle.height);
if (performRowResize(contentHeight, lRectangle)) {
final ILayer layer= cell.getLayer();
layer.doCommand(new DimPositionResizeCommand(layer.getDim(VERTICAL),
cell.getRowPosition(),
(int) Math.min(contentHeight + contentToCellDiff, 0x7fff) ));
}
if (numberOfNewLines == 1) {
final long contentWidth= Math.min(getWidthFromCache(gc, text), lRectangle.width);
gc.drawText(
text,
safe(lRectangle.x + CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, lRectangle, contentWidth) + this.spacing),
safe(lRectangle.y + CellStyleUtil.getVerticalAlignmentPadding(cellStyle, lRectangle, contentHeight) + this.spacing),
SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER
);
if (underline || strikethrough) {
//start x of line= start x of text
final int x= safe(lRectangle.x + CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, lRectangle, contentWidth) + this.spacing);
//y= start y of text
final int y= safe(lRectangle.y + CellStyleUtil.getVerticalAlignmentPadding(cellStyle, lRectangle, contentHeight) + this.spacing);
//check and draw underline and strikethrough separately so it is possible to combine both
if (underline) {
//y= start y of text + font height
// - half of the font descent so the underline is between the baseline and the bottom
final int underlineY= safe(y + fontHeight - (gc.getFontMetrics().getDescent() / 2));
gc.drawLine(
x,
underlineY,
x + gc.textExtent(text).x,
underlineY);
}
if (strikethrough) {
//y= start y of text + half of font height + ascent so lower case characters are
//also strikethrough
final int strikeY= safe(y + (fontHeight / 2) + (gc.getFontMetrics().getLeading() / 2));
gc.drawLine(
x,
strikeY,
x + gc.textExtent(text).x,
strikeY);
}
}
}
else {
//draw every line by itself because of the alignment, otherwise the whole text
//is always aligned right
long yStartPos= lRectangle.y + CellStyleUtil.getVerticalAlignmentPadding(cellStyle, lRectangle, contentHeight);
final String[] lines= text.split("\n"); //$NON-NLS-1$
for (final String line : lines) {
final long lineContentWidth= Math.min(getWidthFromCache(gc, line), lRectangle.width);
gc.drawText(
line,
safe(lRectangle.x + CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, lRectangle, lineContentWidth) + this.spacing),
safe(yStartPos + this.spacing),
SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER
);
if (underline || strikethrough) {
//start x of line= start x of text
final int x= safe(lRectangle.x + CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, lRectangle, lineContentWidth) + this.spacing);
//y= start y of text
final int y= safe(yStartPos + this.spacing);
//check and draw underline and strikethrough separately so it is possible to combine both
if (underline) {
//y= start y of text + font height
// - half of the font descent so the underline is between the baseline and the bottom
final int underlineY= safe(y + fontHeight - (gc.getFontMetrics().getDescent() / 2));
gc.drawLine(
x,
underlineY,
x + gc.textExtent(line).x,
underlineY);
}
if (strikethrough) {
//y= start y of text + half of font height + ascent so lower case characters are
//also strikethrough
final int strikeY= safe(y + (fontHeight / 2) + (gc.getFontMetrics().getLeading() / 2));
gc.drawLine(
x,
strikeY,
x + gc.textExtent(line).x,
strikeY);
}
}
//after every line calculate the y start pos new
yStartPos+= fontHeight;
}
}
gc.setClipping(originalClipping);
}
}
// @Override
// protected void setNewMinLength(ILayerCell cell, long contentWidth) {
// long cellLength= cell.getBounds().width;
// if (cellLength < contentWidth) {
// //execute DimPositionResizeCommand
// ILayer layer= cell.getLayer();
// layer.doCommand(new DimPositionResizeCommand(layer, cell.getColumnPosition(),
// contentWidth));
// }
// }
//
// @Override
// protected long calculatePadding(ILayerCell cell, long availableLength) {
// return cell.getBounds().width - availableLength;
// }
/**
* Checks if a row resize needs to be triggered.
* @param contentHeight The necessary height to show the content completely
* @param lRectangle The available rectangle to render to
* @return <code>true</code> if a row resize needs to be performed, <code>false</code> if not
*/
protected boolean performRowResize(final long contentHeight, final LRectangle lRectangle) {
return (this.calculateByTextHeight && (contentHeight > lRectangle.height));
}
}