blob: 2c24fa8a1b15052d27e9b7216ff176644e8cfc7a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2020 Original 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 authors and others - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.painter.layer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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.painter.cell.ICellPainter;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
public class CellLayerPainter implements ILayerPainter {
private ILayer natLayer;
private Map<Integer, Integer> horizontalPositionToPixelMap;
private Map<Integer, Integer> verticalPositionToPixelMap;
private final boolean clipLeft;
private final boolean clipTop;
/**
* Create a default CellLayerPainter with default clipping behaviour.
*/
public CellLayerPainter() {
this(false, false);
}
/**
* Create a CellLayerPainter with specified clipping behaviour.
*
* @param clipLeft
* Configure the rendering behaviour when cells overlap. If set
* to <code>true</code> the left cell will be clipped, if set to
* <code>false</code> the right cell will be clipped. The default
* value is <code>false</code>.
* @param clipTop
* Configure the rendering behaviour when cells overlap. If set
* to <code>true</code> the top cell will be clipped, if set to
* <code>false</code> the bottom cell will be clipped. The
* default value is <code>false</code>.
*/
public CellLayerPainter(boolean clipLeft, boolean clipTop) {
this.clipLeft = clipLeft;
this.clipTop = clipTop;
}
@Override
public void paintLayer(ILayer natLayer, GC gc, int xOffset, int yOffset,
Rectangle pixelRectangle, IConfigRegistry configRegistry) {
if (pixelRectangle.width <= 0 || pixelRectangle.height <= 0) {
return;
}
this.natLayer = natLayer;
Rectangle positionRectangle = getPositionRectangleFromPixelRectangle(natLayer, pixelRectangle);
calculateDimensionInfo(positionRectangle);
Collection<ILayerCell> spannedCells = new HashSet<>();
for (int columnPosition = positionRectangle.x; columnPosition < positionRectangle.x
+ positionRectangle.width; columnPosition++) {
for (int rowPosition = positionRectangle.y; rowPosition < positionRectangle.y
+ positionRectangle.height; rowPosition++) {
if (columnPosition == -1 || rowPosition == -1) {
continue;
}
ILayerCell cell = natLayer.getCellByPosition(columnPosition, rowPosition);
if (cell != null) {
if (cell.isSpannedCell()) {
spannedCells.add(cell);
} else {
paintCell(cell, gc, configRegistry);
}
}
}
}
for (ILayerCell cell : spannedCells) {
paintCell(cell, gc, configRegistry);
}
}
/**
* Determines the rendering behavior when two cells overlap. If
* <code>true</code>, the left cell will be clipped. If <code>false</code>,
* the right cell will be clipped. Typically this value is changed in
* conjunction with split viewports.
*
* @param position
* The column position for which the clipping behaviour is
* requested. By default for all columns the same clipping
* behaviour is used. Only for special cases like split viewports
* with one header, per position a different behaviour may be
* needed.
* @return <code>true</code> if the left cell will be clipped,
* <code>false</code> if the right cell will be clipped.
*/
protected boolean isClipLeft(int position) {
return this.clipLeft;
}
/**
* Determines the rendering behavior when two cells overlap. If
* <code>true</code>, the top cell will be clipped. If <code>false</code>,
* the bottom cell will be clipped. Typically this value is changed in
* conjunction with split viewports.
*
* @param position
* The row position for which the clipping behaviour is
* requested. By default for all rows the same clipping behaviour
* is used. Only for special cases like split viewports with one
* header, per position a different behaviour may be needed.
* @return <code>true</code> if the top cell will be clipped,
* <code>false</code> if the bottom cell will be clipped.
*/
protected boolean isClipTop(int position) {
return this.clipTop;
}
private void calculateDimensionInfo(Rectangle positionRectangle) {
{
this.horizontalPositionToPixelMap = new HashMap<>();
final int startPosition = positionRectangle.x;
final int endPosition = startPosition + positionRectangle.width;
int previousEndX = (startPosition > 0)
? this.natLayer.getStartXOfColumnPosition(startPosition - 1)
+ this.natLayer.getColumnWidthByPosition(startPosition - 1)
: Integer.MIN_VALUE;
for (int position = startPosition; position < endPosition; position++) {
int startX = this.natLayer.getStartXOfColumnPosition(position);
this.horizontalPositionToPixelMap.put(
position,
isClipLeft(position) ? startX : Math.max(startX, previousEndX));
previousEndX = startX + this.natLayer.getColumnWidthByPosition(position);
}
if (endPosition < this.natLayer.getColumnCount()) {
int startX = this.natLayer.getStartXOfColumnPosition(endPosition);
this.horizontalPositionToPixelMap.put(endPosition, Math.max(startX, previousEndX));
}
}
{
this.verticalPositionToPixelMap = new HashMap<>();
final int startPosition = positionRectangle.y;
final int endPosition = startPosition + positionRectangle.height;
int previousEndY = (startPosition > 0)
? this.natLayer.getStartYOfRowPosition(startPosition - 1)
+ this.natLayer.getRowHeightByPosition(startPosition - 1)
: Integer.MIN_VALUE;
for (int position = startPosition; position < endPosition; position++) {
int startY = this.natLayer.getStartYOfRowPosition(position);
this.verticalPositionToPixelMap.put(
position,
isClipTop(position) ? startY : Math.max(startY, previousEndY));
previousEndY = startY + this.natLayer.getRowHeightByPosition(position);
}
if (endPosition < this.natLayer.getRowCount()) {
int startY = this.natLayer.getStartYOfRowPosition(endPosition);
this.verticalPositionToPixelMap.put(endPosition, Math.max(startY, previousEndY));
}
}
}
@Override
public Rectangle adjustCellBounds(int columnPosition, int rowPosition, Rectangle cellBounds) {
return cellBounds;
}
protected Rectangle getPositionRectangleFromPixelRectangle(ILayer natLayer, Rectangle pixelRectangle) {
int columnPositionOffset = natLayer.getColumnPositionByX(pixelRectangle.x);
int rowPositionOffset = natLayer.getRowPositionByY(pixelRectangle.y);
int numColumns = natLayer.getColumnPositionByX(
Math.min(natLayer.getWidth(), pixelRectangle.x + pixelRectangle.width) - 1)
- columnPositionOffset + 1;
int numRows = natLayer.getRowPositionByY(
Math.min(natLayer.getHeight(), pixelRectangle.y + pixelRectangle.height) - 1)
- rowPositionOffset + 1;
return new Rectangle(
columnPositionOffset,
rowPositionOffset,
numColumns,
numRows);
}
protected void paintCell(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {
ILayer layer = cell.getLayer();
int columnPosition = cell.getColumnPosition();
int rowPosition = cell.getRowPosition();
ICellPainter cellPainter = layer.getCellPainter(columnPosition, rowPosition, cell, configRegistry);
Rectangle adjustedCellBounds = layer
.getLayerPainter()
.adjustCellBounds(columnPosition, rowPosition, cell.getBounds());
if (cellPainter != null) {
Rectangle originalClipping = gc.getClipping();
int startX = getStartXOfColumnPosition(columnPosition);
int startY = getStartYOfRowPosition(rowPosition);
int endX = getStartXOfColumnPosition(cell.getOriginColumnPosition() + cell.getColumnSpan());
int endY = getStartYOfRowPosition(cell.getOriginRowPosition() + cell.getRowSpan());
Rectangle cellClipBounds = originalClipping.intersection(
new Rectangle(startX, startY, endX - startX, endY - startY));
gc.setClipping(cellClipBounds.intersection(adjustedCellBounds));
cellPainter.paintCell(cell, gc, adjustedCellBounds, configRegistry);
gc.setClipping(originalClipping);
}
}
protected int getStartXOfColumnPosition(final int columnPosition) {
if (columnPosition < this.natLayer.getColumnCount()) {
Integer start = this.horizontalPositionToPixelMap.get(columnPosition);
if (start == null) {
start = this.natLayer.getStartXOfColumnPosition(columnPosition);
if (columnPosition > 0) {
int start2 = this.natLayer.getStartXOfColumnPosition(columnPosition - 1)
+ this.natLayer.getColumnWidthByPosition(columnPosition - 1);
if (start2 > start.intValue()) {
start = start2;
}
}
this.horizontalPositionToPixelMap.put(columnPosition, start);
}
return start.intValue();
} else {
return this.natLayer.getWidth();
}
}
protected int getStartYOfRowPosition(final int rowPosition) {
if (rowPosition < this.natLayer.getRowCount()) {
Integer start = this.verticalPositionToPixelMap.get(rowPosition);
if (start == null) {
start = this.natLayer.getStartYOfRowPosition(rowPosition);
if (rowPosition > 0) {
int start2 = this.natLayer.getStartYOfRowPosition(rowPosition - 1)
+ this.natLayer.getRowHeightByPosition(rowPosition - 1);
if (start2 > start.intValue()) {
start = start2;
}
}
this.verticalPositionToPixelMap.put(rowPosition, start);
}
return start.intValue();
} else {
return this.natLayer.getHeight();
}
}
}