blob: 0aae92de4719d191cf91d109de738d5fb866bc4c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2019 Original authors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Original authors and others - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 454506
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.painter.cell;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.resize.command.ColumnResizeCommand;
import org.eclipse.nebula.widgets.nattable.resize.command.RowResizeCommand;
import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil;
import org.eclipse.nebula.widgets.nattable.style.IStyle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
/**
* 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 {
/**
* Flag to configure whether the preferred height is calculated on the
* wrapped text or on the text that would be rendered without wrapping.
* Default is <code>false</code>.
*/
private boolean calculateWrappedHeight = false;
/**
* Creates a {@link TextPainter} that does not wrap text and paints the
* background.
*/
public TextPainter() {
this(false, true);
}
/**
* @param wrapText
* split text over multiple lines
* @param paintBg
* skips painting the background if is FALSE
*/
public TextPainter(boolean wrapText, 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(boolean wrapText, boolean paintBg, 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(boolean wrapText, boolean paintBg, boolean calculate) {
this(wrapText, paintBg, 0, calculate);
}
/**
* @param wrapText
* split text over multiple lines
* @param paintBg
* skips painting the background if is FALSE
* @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,
boolean calculateByTextLength, boolean calculateByTextHeight) {
this(wrapText, paintBg, 0, calculateByTextLength, calculateByTextHeight);
}
/**
* @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(boolean wrapText, boolean paintBg, int spacing, 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, int spacing,
boolean calculateByTextLength, boolean calculateByTextHeight) {
super(wrapText, paintBg, spacing, calculateByTextLength, calculateByTextHeight);
}
@Override
public int getPreferredWidth(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {
setupGCFromConfig(gc, CellStyleUtil.getCellStyle(cell, configRegistry));
return getLengthFromCache(gc, convertDataType(cell, configRegistry)) + (this.spacing * 2) + 1;
}
@Override
public int getPreferredHeight(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {
setupGCFromConfig(gc, CellStyleUtil.getCellStyle(cell, configRegistry));
String text = convertDataType(cell, configRegistry);
if (!this.calculateWrappedHeight) {
return gc.textExtent(text).y + (this.spacing * 2) + 1 + (getNumberOfNewLines(text) - 1) * this.lineSpacing;
} else {
Rectangle adjustedBounds = cell.getLayer().getLayerPainter().adjustCellBounds(
cell.getColumnPosition(),
cell.getRowPosition(),
cell.getBounds());
text = getTextToDisplay(cell, gc, adjustedBounds.width, text);
int numberOfNewLines = getNumberOfNewLines(text);
return (gc.getFontMetrics().getHeight() * numberOfNewLines) + (this.lineSpacing * (numberOfNewLines - 1)) + (this.spacing * 2);
}
}
@Override
public void paintCell(ILayerCell cell, GC gc, Rectangle rectangle, IConfigRegistry configRegistry) {
if (this.paintBg) {
super.paintCell(cell, gc, rectangle, configRegistry);
}
if (this.paintFg) {
Rectangle originalClipping = gc.getClipping();
gc.setClipping(rectangle.intersection(originalClipping));
IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry);
setupGCFromConfig(gc, cellStyle);
int fontHeight = gc.getFontMetrics().getHeight();
String text = convertDataType(cell, configRegistry);
// Draw Text
text = getTextToDisplay(cell, gc, rectangle.width, text);
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)
int contentHeight = (fontHeight * numberOfNewLines) + (this.lineSpacing * (numberOfNewLines - 1)) + (this.spacing * 2);
int contentToCellDiff = (cell.getBounds().height - rectangle.height);
if (performRowResize(contentHeight, rectangle)) {
ILayer layer = cell.getLayer();
int rowPosition = cell.getRowPosition();
if (cell.isSpannedCell()) {
// if spanned only resize bottom row and reduce height by
// upper row heights to resize to only the necessary height
rowPosition = cell.getOriginRowPosition() + cell.getRowSpan() - 1;
for (int i = cell.getOriginRowPosition(); i < rowPosition; i++) {
contentHeight -= layer.getRowHeightByPosition(i);
}
}
layer.doCommand(
new RowResizeCommand(layer, rowPosition, contentHeight + contentToCellDiff, true));
}
if (numberOfNewLines == 1) {
int contentWidth = Math.min(getLengthFromCache(gc, text), rectangle.width);
gc.drawText(
text,
rectangle.x + CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, rectangle, contentWidth) + this.spacing,
rectangle.y + CellStyleUtil.getVerticalAlignmentPadding(cellStyle, rectangle, contentHeight) + this.spacing,
SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER | SWT.DRAW_TAB);
// start x of line = start x of text
int x = rectangle.x
+ CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, rectangle, contentWidth)
+ this.spacing;
// y = start y of text
int y = rectangle.y
+ CellStyleUtil.getVerticalAlignmentPadding(cellStyle, rectangle, contentHeight)
+ this.spacing;
int length = gc.textExtent(text).x;
paintDecoration(cellStyle, gc, x, y, length, fontHeight);
} else {
// draw every line by itself because of the alignment, otherwise
// the whole text is always aligned right
int yStartPos = rectangle.y + CellStyleUtil.getVerticalAlignmentPadding(cellStyle, rectangle, contentHeight);
String[] lines = text.split("\n"); //$NON-NLS-1$
for (String line : lines) {
int lineContentWidth = Math.min(getLengthFromCache(gc, line), rectangle.width);
gc.drawText(
line,
rectangle.x + CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, rectangle, lineContentWidth) + this.spacing,
yStartPos + this.spacing,
SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER | SWT.DRAW_TAB);
// start x of line = start x of text
int x = rectangle.x
+ CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, rectangle, lineContentWidth)
+ this.spacing;
// y = start y of text
int y = yStartPos + this.spacing;
int length = gc.textExtent(line).x;
paintDecoration(cellStyle, gc, x, y, length, fontHeight);
// after every line calculate the y start pos new
yStartPos += fontHeight;
yStartPos += this.lineSpacing;
}
}
gc.setClipping(originalClipping);
resetGC(gc);
}
}
@Override
protected void setNewMinLength(ILayerCell cell, int contentWidth) {
int cellLength = cell.getBounds().width;
if (cellLength < contentWidth) {
ILayer layer = cell.getLayer();
int columnPosition = cell.getColumnPosition();
if (cell.isSpannedCell()) {
// if spanned only resize rightmost column and reduce width by
// left column widths to resize to only the necessary width
columnPosition = cell.getOriginColumnPosition() + cell.getColumnSpan() - 1;
for (int i = cell.getOriginColumnPosition(); i < columnPosition; i++) {
contentWidth -= layer.getColumnWidthByPosition(i);
}
}
layer.doCommand(
new ColumnResizeCommand(layer, columnPosition, contentWidth, true));
}
}
@Override
protected int calculatePadding(ILayerCell cell, int 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 rectangle
* 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(int contentHeight, Rectangle rectangle) {
return ((contentHeight > rectangle.height) && this.calculateByTextHeight);
}
/**
*
* @return Whether the preferred height is calculated on the wrapped text or
* on the text that would be rendered without wrapping. Default is
* <code>false</code>.
* @since 1.6
*/
public boolean isCalculateWrappedHeight() {
return this.calculateWrappedHeight;
}
/**
* Configure how
* {@link #getPreferredHeight(ILayerCell, GC, IConfigRegistry)} calculates
* the height. Either based on the text as is, or taking text-wrapping into
* account if enabled.
*
* @param calculateWrappedHeight
* <code>true</code> if the preferred height should be calculated
* on the wrapped text, <code>false</code> if the preferred
* height should be calculated without wrapping.
* @since 1.6
*/
public void setCalculateWrappedHeight(boolean calculateWrappedHeight) {
this.calculateWrappedHeight = calculateWrappedHeight;
}
}