/******************************************************************************* * Copyright (c) 2012, 2016 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 * neal zhang <nujiah001@126.com> - change some methods and fields visibility * Loris Securo <lorissek@gmail.com> - Bug 500750 ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; 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.BorderPainter; import org.eclipse.nebula.widgets.nattable.painter.cell.BorderPainter.BorderCell; import org.eclipse.nebula.widgets.nattable.painter.cell.BorderPainter.PaintModeEnum; import org.eclipse.nebula.widgets.nattable.painter.layer.GridLineCellLayerPainter; import org.eclipse.nebula.widgets.nattable.style.BorderStyle; import org.eclipse.nebula.widgets.nattable.style.BorderStyle.BorderModeEnum; import org.eclipse.nebula.widgets.nattable.style.BorderStyle.LineStyleEnum; import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.style.IStyle; import org.eclipse.nebula.widgets.nattable.style.SelectionStyleLabels; import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; /** * Specialised GridLineCellLayerPainter that renders an additional border around * selected cells. By default the additional selection anchor border style is * black dotted one pixel sized line. This style can be configured via * ConfigRegistry. * * @see SelectionStyleLabels#SELECTION_ANCHOR_GRID_LINE_STYLE */ public class SelectionLayerPainter extends GridLineCellLayerPainter { /** * Create a SelectionLayerPainter that renders grid lines in the specified * color and uses the default clipping behaviour. * * @param gridColor * The color that should be used to render the grid lines. */ public SelectionLayerPainter(final Color gridColor) { super(gridColor); } /** * Create a SelectionLayerPainter that renders gray grid lines and uses the * default clipping behaviour. */ public SelectionLayerPainter() { super(); } /** * Create a SelectionLayerPainter that renders grid lines in the specified * color and uses the specified clipping behaviour. * * @param gridColor * The color that should be used to render the grid lines. * @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 SelectionLayerPainter(final Color gridColor, boolean clipLeft, boolean clipTop) { super(gridColor, clipLeft, clipTop); } /** * Create a SelectionLayerPainter that renders gray grid lines and uses the * 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 SelectionLayerPainter(boolean clipLeft, boolean clipTop) { this(GUIHelper.COLOR_GRAY, clipLeft, clipTop); } @Override public void paintLayer( ILayer natLayer, GC gc, int xOffset, int yOffset, Rectangle pixelRectangle, IConfigRegistry configRegistry) { super.paintLayer(natLayer, gc, xOffset, yOffset, pixelRectangle, configRegistry); Rectangle positionRectangle = getPositionRectangleFromPixelRectangle(natLayer, pixelRectangle); int columnPositionOffset = positionRectangle.x; int rowPositionOffset = positionRectangle.y; // nothing to draw, we exit if (positionRectangle.width <= 0 || positionRectangle.height <= 0) { return; } BorderCell[][] borderCells; boolean atLeastOne = false; PaintModeEnum paintMode; // tentative way to know that this is a single cell update if (positionRectangle.width <= 2 && positionRectangle.height <= 2) { // In order to correctly paint the selection borders in case of // single cell updates we need to consider also the adjacent cells. // Therefore we try to retrieve also cells that are outside the // pixelRectangle but still inside our layer. // +2 because we are going to read also adjacent cells in the // extremities borderCells = new BorderCell[positionRectangle.height + 2][positionRectangle.width + 2]; // we need to repaint only the internal borders of the external // cells paintMode = PaintModeEnum.NO_EXTERNAL_BORDERS; // -1/+1 because we are going to read also adjacent cells in the // extremities for (int columnPosition = columnPositionOffset - 1, ix = 0; columnPosition < columnPositionOffset + positionRectangle.width + 1; columnPosition++, ix++) { for (int rowPosition = rowPositionOffset - 1, iy = 0; rowPosition < rowPositionOffset + positionRectangle.height + 1; rowPosition++, iy++) { boolean insideBorder = false; Rectangle cellBounds = null; ILayerCell currentCell = natLayer.getCellByPosition(columnPosition, rowPosition); if (currentCell != null) { cellBounds = currentCell.getBounds(); // the cell should be considered only if it is in our // layer boolean toBeConsidered = isInCurrentLayer(ix, iy, xOffset, yOffset, cellBounds, borderCells); if (toBeConsidered && isSelected(currentCell)) { insideBorder = true; atLeastOne = true; } } Rectangle fixedBounds = fixBoundsInGridLines(cellBounds, xOffset, yOffset); BorderCell borderCell = new BorderCell(fixedBounds, insideBorder); borderCells[iy][ix] = borderCell; } } } else { borderCells = new BorderCell[positionRectangle.height][positionRectangle.width]; paintMode = PaintModeEnum.ALL; for (int columnPosition = columnPositionOffset, ix = 0; columnPosition < columnPositionOffset + positionRectangle.width; columnPosition++, ix++) { for (int rowPosition = rowPositionOffset, iy = 0; rowPosition < rowPositionOffset + positionRectangle.height; rowPosition++, iy++) { boolean insideBorder = false; Rectangle cellBounds = null; ILayerCell currentCell = natLayer.getCellByPosition(columnPosition, rowPosition); if (currentCell != null) { // In case of spanned cells the border painter needs to // know the bounds of adjacent cells even if they are // not selected. This is the reason why we get the // bounds also for non selected cells. cellBounds = currentCell.getBounds(); if (isSelected(currentCell)) { insideBorder = true; atLeastOne = true; } } Rectangle fixedBounds = fixBoundsInGridLines(cellBounds, xOffset, yOffset); BorderCell borderCell = new BorderCell(fixedBounds, insideBorder); borderCells[iy][ix] = borderCell; } } } if (atLeastOne) { // Save gc settings int originalLineStyle = gc.getLineStyle(); int originalLineWidth = gc.getLineWidth(); Color originalForeground = gc.getForeground(); BorderStyle borderStyle = getBorderStyle(configRegistry); BorderPainter borderPainter = new BorderPainter(borderCells, borderStyle, paintMode); borderPainter.paintBorder(gc); // Restore original gc settings gc.setLineStyle(originalLineStyle); gc.setLineWidth(originalLineWidth); gc.setForeground(originalForeground); } } private boolean isSelected(ILayerCell cell) { return (cell.getDisplayMode() == DisplayMode.SELECT || cell.getDisplayMode() == DisplayMode.SELECT_HOVER); } /** * Returns a rectangle that will cover the left and top grid lines, if they * are present. * * @param cellBounds * the rectangle that needs to be considered * @param xOffset * the starting x coordinate of the area we can draw on. The fix * will not be applied if the <code>cellBounds</code> are placed * on this limit. * @param yOffset * the starting y coordinate of the area we can draw on. The fix * will not be applied if the <code>cellBounds</code> are placed * on this limit. * * @since 1.5 */ protected Rectangle fixBoundsInGridLines(Rectangle cellBounds, int xOffset, int yOffset) { if (cellBounds == null) { return null; } Rectangle fixedBounds = new Rectangle(cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height); // when grid lines are rendered we want the border // to cover them, otherwise we remain inside the // cell if (this.renderGridLines && fixedBounds.x != 0 && fixedBounds.x != xOffset) { fixedBounds.x--; fixedBounds.width++; } if (this.renderGridLines && fixedBounds.y != 0 && fixedBounds.y != yOffset) { fixedBounds.y--; fixedBounds.height++; } return fixedBounds; } /** * Tries to detect if the cell is part of the current layer. It does so * using xOffset/yOffset (which are not affected by single cell updates) and * detecting overlapping of cells, which should not be possible in the same * layer. It's not perfect, there might be false positives. * * @since 1.5 */ protected boolean isInCurrentLayer(int ix, int iy, int xOffset, int yOffset, Rectangle cellBounds, BorderCell[][] borderCells) { // If the cell bounds are not inside the x/y offset, we consider it part // of another layer if (ix == 0) { if (cellBounds.x + cellBounds.width <= xOffset) { return false; } } if (iy == 0) { if (cellBounds.y + cellBounds.height <= yOffset) { return false; } } // if the previous cell is overlapping the current cell we consider it // part of another layer if (ix == 1) { if (borderCells[iy][ix - 1].isInsideBorder) { Rectangle prevCellBounds = borderCells[iy][ix - 1].bounds; if (prevCellBounds.x + prevCellBounds.width > cellBounds.x) { borderCells[iy][ix - 1].isInsideBorder = false; } } } if (iy == 1) { if (borderCells[iy - 1][ix].isInsideBorder) { Rectangle prevCellBounds = borderCells[iy - 1][ix].bounds; if (prevCellBounds.y + prevCellBounds.height > cellBounds.y) { borderCells[iy - 1][ix].isInsideBorder = false; } } } // it's an external cell and it's getting overlapped by the previous // cell, we consider it part of another layer if (ix == borderCells[iy].length - 1) { Rectangle prevCellBounds = borderCells[iy][ix - 1].bounds; if (prevCellBounds.x + prevCellBounds.width > cellBounds.x) { return false; } } if (iy == borderCells.length - 1) { Rectangle prevCellBounds = borderCells[iy - 1][ix].bounds; if (prevCellBounds.y + prevCellBounds.height > cellBounds.y) { return false; } } return true; } /** * @deprecated Use {@link #getBorderStyle} instead. */ @Deprecated protected void applyBorderStyle(GC gc, IConfigRegistry configRegistry) { BorderStyle borderStyle = configRegistry.getConfigAttribute( SelectionConfigAttributes.SELECTION_GRID_LINE_STYLE, DisplayMode.SELECT); // check for backwards compatibility style configuration if (borderStyle == null) { // Note: If there is no style configured for the // SelectionStyleLabels.SELECTION_ANCHOR_GRID_LINE_STYLE // label, the style configured for DisplayMode.SELECT will be // retrieved by this call. // Ensure that the selection style configuration does not contain a // border style configuration to avoid strange rendering behavior. // By default there is no border configuration added, so there // shouldn't be issues with backwards compatibility. And if there // are some, they can be solved easily by adding the necessary // border style configuration. IStyle cellStyle = configRegistry.getConfigAttribute( CellConfigAttributes.CELL_STYLE, DisplayMode.SELECT, SelectionStyleLabels.SELECTION_ANCHOR_GRID_LINE_STYLE); borderStyle = cellStyle != null ? cellStyle.getAttributeValue(CellStyleAttributes.BORDER_STYLE) : null; } // if there is no border style configured, use the default one for // backwards compatibility if (borderStyle == null) { gc.setLineStyle(SWT.LINE_CUSTOM); gc.setLineDash(new int[] { 1, 1 }); gc.setForeground(GUIHelper.COLOR_BLACK); } else { gc.setLineStyle(LineStyleEnum.toSWT(borderStyle.getLineStyle())); gc.setLineWidth(borderStyle.getThickness()); gc.setForeground(borderStyle.getColor()); } } /** * Get the border style that should be used to render the border for cells * that are currently selected. Checks the {@link IConfigRegistry} for a * registered {@link IStyle} for the * {@link SelectionConfigAttributes#SELECTION_GRID_LINE_STYLE} label or the * {@link SelectionStyleLabels#SELECTION_ANCHOR_GRID_LINE_STYLE} label. If * none is registered, a default line style will be returned. * * @param configRegistry * The {@link IConfigRegistry} to retrieve the style information * from. * * @return The border style that should be used * * @since 1.5 */ protected BorderStyle getBorderStyle(IConfigRegistry configRegistry) { BorderStyle borderStyle = configRegistry.getConfigAttribute( SelectionConfigAttributes.SELECTION_GRID_LINE_STYLE, DisplayMode.SELECT); // check for backwards compatibility style configuration if (borderStyle == null) { // Note: If there is no style configured for the // SelectionStyleLabels.SELECTION_ANCHOR_GRID_LINE_STYLE // label, the style configured for DisplayMode.SELECT will be // retrieved by this call. // Ensure that the selection style configuration does not contain a // border style configuration to avoid strange rendering behavior. // By default there is no border configuration added, so there // shouldn't be issues with backwards compatibility. And if there // are some, they can be solved easily by adding the necessary // border style configuration. IStyle cellStyle = configRegistry.getConfigAttribute( CellConfigAttributes.CELL_STYLE, DisplayMode.SELECT, SelectionStyleLabels.SELECTION_ANCHOR_GRID_LINE_STYLE); borderStyle = cellStyle != null ? cellStyle.getAttributeValue(CellStyleAttributes.BORDER_STYLE) : null; } // if there is no border style configured, use the default if (borderStyle == null) { borderStyle = new BorderStyle(1, GUIHelper.COLOR_BLACK, LineStyleEnum.DOTTED, BorderModeEnum.CENTERED); } return borderStyle; } }