/*******************************************************************************
* Copyright (c) 2012, 2013 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
******************************************************************************/
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<ILayerCell>();
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.
*/
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.
*/
protected boolean isClipTop(int position) {
return this.clipTop;
}
private void calculateDimensionInfo(Rectangle positionRectangle) {
{
horizontalPositionToPixelMap = new HashMap<Integer, Integer>();
final int startPosition = positionRectangle.x;
final int endPosition = startPosition + positionRectangle.width;
int previousEndX = (startPosition > 0) ? natLayer
.getStartXOfColumnPosition(startPosition - 1)
+ natLayer.getColumnWidthByPosition(startPosition - 1)
: Integer.MIN_VALUE;
for (int position = startPosition; position < endPosition; position++) {
int startX = natLayer.getStartXOfColumnPosition(position);
horizontalPositionToPixelMap.put(
position,
isClipLeft(position) ? startX : Math.max(startX,
previousEndX));
previousEndX = startX
+ natLayer.getColumnWidthByPosition(position);
}
if (endPosition < natLayer.getColumnCount()) {
int startX = natLayer.getStartXOfColumnPosition(endPosition);
horizontalPositionToPixelMap.put(endPosition,
Math.max(startX, previousEndX));
}
}
{
verticalPositionToPixelMap = new HashMap<Integer, Integer>();
final int startPosition = positionRectangle.y;
final int endPosition = startPosition + positionRectangle.height;
int previousEndY = (startPosition > 0) ? natLayer
.getStartYOfRowPosition(startPosition - 1)
+ natLayer.getRowHeightByPosition(startPosition - 1)
: Integer.MIN_VALUE;
for (int position = startPosition; position < endPosition; position++) {
int startY = natLayer.getStartYOfRowPosition(position);
verticalPositionToPixelMap.put(
position,
isClipTop(position) ? startY : Math.max(startY,
previousEndY));
previousEndY = startY
+ natLayer.getRowHeightByPosition(position);
}
if (endPosition < natLayer.getRowCount()) {
int startY = natLayer.getStartYOfRowPosition(endPosition);
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 < natLayer.getColumnCount()) {
Integer start = horizontalPositionToPixelMap.get(columnPosition);
if (start == null) {
start = Integer.valueOf(natLayer
.getStartXOfColumnPosition(columnPosition));
if (columnPosition > 0) {
int start2 = natLayer
.getStartXOfColumnPosition(columnPosition - 1)
+ natLayer
.getColumnWidthByPosition(columnPosition - 1);
if (start2 > start.intValue()) {
start = Integer.valueOf(start2);
}
}
horizontalPositionToPixelMap.put(columnPosition, start);
}
return start.intValue();
} else {
return natLayer.getWidth();
}
}
protected int getStartYOfRowPosition(final int rowPosition) {
if (rowPosition < natLayer.getRowCount()) {
Integer start = verticalPositionToPixelMap.get(rowPosition);
if (start == null) {
start = Integer.valueOf(natLayer
.getStartYOfRowPosition(rowPosition));
if (rowPosition > 0) {
int start2 = natLayer
.getStartYOfRowPosition(rowPosition - 1)
+ natLayer.getRowHeightByPosition(rowPosition - 1);
if (start2 > start.intValue()) {
start = Integer.valueOf(start2);
}
}
verticalPositionToPixelMap.put(rowPosition, start);
}
return start.intValue();
} else {
return natLayer.getHeight();
}
}
}