| /******************************************************************************* |
| * 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.Map; |
| |
| import org.eclipse.collections.api.factory.Sets; |
| 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 = Sets.mutable.empty(); |
| |
| for (int rowPosition = positionRectangle.y; rowPosition < positionRectangle.y |
| + positionRectangle.height; rowPosition++) { |
| for (int columnPosition = positionRectangle.x; columnPosition < positionRectangle.x |
| + positionRectangle.width; columnPosition++) { |
| 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(); |
| } |
| } |
| |
| } |