/***************************************************************************** * Copyright (c) 2016 Loris Securo. * * 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: * Loris Securo <lorissek@gmail.com> - Initial API and implementation * *****************************************************************************/ package org.eclipse.nebula.widgets.nattable.painter.cell; 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.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; /** * Draws borders based on a 2D array of {@link BorderCell} and styled * accordingly to a {@link BorderStyle}. * * @author Loris Securo * * @since 1.5 */ public class BorderPainter { protected BorderCell borderCells[][]; protected BorderStyle borderStyle; protected PaintModeEnum paintMode; public enum PaintModeEnum { /** * The border will be painted completely for all the cells. */ ALL, /** * The border will be painted completely for the internal cells. The * border will not be painted for the external cells. */ NO_EXTERNAL_CELLS, /** * The border will be painted completely for the internal cells. For the * external cells, only the inner borders will be painted. */ NO_EXTERNAL_BORDERS; } /** * Utility class used to return coordinates and lengths at once. */ public static class LineValues { public int x; public int y; public int lenght; public LineValues(int x, int y, int lenght) { this.x = x; this.y = y; this.lenght = lenght; } } /** * Stores the bounds and the state of a cell that will be used to draw * borders. */ public static class BorderCell { public Rectangle bounds; public boolean isInsideBorder; public BorderCell(Rectangle bounds) { this(bounds, true); } public BorderCell(Rectangle bounds, boolean isInsideBorder) { this.bounds = bounds; this.isInsideBorder = isInsideBorder; } } public BorderPainter(BorderCell borderCells[][], BorderStyle borderStyle) { this(borderCells, borderStyle, PaintModeEnum.ALL); } public BorderPainter(BorderCell borderCells[][], BorderStyle borderStyle, PaintModeEnum paintMode) { this.borderCells = borderCells; this.borderStyle = borderStyle; this.paintMode = paintMode; } public void paintBorder(GC gc) { // apply the border style if (this.borderStyle != null) { gc.setLineStyle(LineStyleEnum.toSWT(this.borderStyle.getLineStyle())); gc.setLineWidth(this.borderStyle.getThickness()); gc.setForeground(this.borderStyle.getColor()); } for (int iy = 0; iy < this.borderCells.length; iy++) { for (int ix = 0; ix < this.borderCells[iy].length; ix++) { // if we don't have to draw borders of external cells, we ignore // them if (this.paintMode == PaintModeEnum.NO_EXTERNAL_CELLS) { if (ix == 0 || iy == 0 || ix == this.borderCells[iy].length - 1 || iy == this.borderCells.length - 1) { continue; } } if (isInside(ix, iy)) { // if we are in an external cell and we don't have to draw // its external borders, we check if the border to draw is // internal or external // left is not in, draw left border if ((this.paintMode != PaintModeEnum.NO_EXTERNAL_BORDERS || ix > 0) && !isInside(ix - 1, iy)) { drawLineLeft(gc, ix, iy); } // right is not in, draw right border if ((this.paintMode != PaintModeEnum.NO_EXTERNAL_BORDERS || ix < this.borderCells[iy].length - 1) && !isInside(ix + 1, iy)) { drawLineRight(gc, ix, iy); } // top is not in, draw top border if ((this.paintMode != PaintModeEnum.NO_EXTERNAL_BORDERS || iy > 0) && !isInside(ix, iy - 1)) { drawLineTop(gc, ix, iy); } // bottom is not in, draw bottom border if ((this.paintMode != PaintModeEnum.NO_EXTERNAL_BORDERS || iy < this.borderCells.length - 1) && !isInside(ix, iy + 1)) { drawLineBottom(gc, ix, iy); } } } } } // The methods getLineValues... deal with spanned cells by looking at // adjacent cells to get the correct line length. // For example: // // ----- // |I|O| <- 1 cell inside, 1 cell outside // ----- // |I I| <- inside spanned cell for 2 // ----- // // when considering the cell in (1,1) it should draw a top border between it // and the outside cell at (1,0); // but it can't use the width of cell (1,1) otherwise it will draw a border // also under the inside cell (0,0); // therefore its upper cell (1,0) is considered to find the correct position // and width of the border line; // in this case: // x = max((1,1).x0, (1,0).x0) // l = min((1,1).x1, (1,0).x1) - x protected LineValues getLineValuesLeft(int ix, int iy) { int y; int l; int x = this.borderCells[iy][ix].bounds.x; if (isValid(ix - 1, iy)) { y = Math.max(this.borderCells[iy][ix].bounds.y, this.borderCells[iy][ix - 1].bounds.y); l = Math.min(this.borderCells[iy][ix].bounds.y + this.borderCells[iy][ix].bounds.height, this.borderCells[iy][ix - 1].bounds.y + this.borderCells[iy][ix - 1].bounds.height) - y; } else { y = this.borderCells[iy][ix].bounds.y; l = this.borderCells[iy][ix].bounds.height; } return new LineValues(x, y, l); } protected LineValues getLineValuesRight(int ix, int iy) { int y; int l; int x = this.borderCells[iy][ix].bounds.x + this.borderCells[iy][ix].bounds.width - 1; if (isValid(ix + 1, iy)) { y = Math.max(this.borderCells[iy][ix].bounds.y, this.borderCells[iy][ix + 1].bounds.y); l = Math.min(this.borderCells[iy][ix].bounds.y + this.borderCells[iy][ix].bounds.height, this.borderCells[iy][ix + 1].bounds.y + this.borderCells[iy][ix + 1].bounds.height) - y; } else { y = this.borderCells[iy][ix].bounds.y; l = this.borderCells[iy][ix].bounds.height; } return new LineValues(x, y, l); } protected LineValues getLineValuesTop(int ix, int iy) { int x; int l; if (isValid(ix, iy - 1)) { x = Math.max(this.borderCells[iy][ix].bounds.x, this.borderCells[iy - 1][ix].bounds.x); l = Math.min(this.borderCells[iy][ix].bounds.x + this.borderCells[iy][ix].bounds.width, this.borderCells[iy - 1][ix].bounds.x + this.borderCells[iy - 1][ix].bounds.width) - x; } else { x = this.borderCells[iy][ix].bounds.x; l = this.borderCells[iy][ix].bounds.width; } int y = this.borderCells[iy][ix].bounds.y; return new LineValues(x, y, l); } protected LineValues getLineValuesBottom(int ix, int iy) { int x; int l; if (isValid(ix, iy + 1)) { x = Math.max(this.borderCells[iy][ix].bounds.x, this.borderCells[iy + 1][ix].bounds.x); l = Math.min(this.borderCells[iy][ix].bounds.x + this.borderCells[iy][ix].bounds.width, this.borderCells[iy + 1][ix].bounds.x + this.borderCells[iy + 1][ix].bounds.width) - x; } else { x = this.borderCells[iy][ix].bounds.x; l = this.borderCells[iy][ix].bounds.width; } int y = this.borderCells[iy][ix].bounds.y + this.borderCells[iy][ix].bounds.height - 1; return new LineValues(x, y, l); } protected void drawLineLeft(GC gc, int ix, int iy) { LineValues lineValues = getLineValuesLeft(ix, iy); int x = lineValues.x; int y = lineValues.y; int l = lineValues.lenght; boolean drawTopCorner = false; boolean drawBottomCorner = false; BorderModeEnum borderMode = this.borderStyle.getBorderMode(); // check if we need to draw the top corner if (!isInside(ix, iy - 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawTopCorner = true; } } else if (isInside(ix - 1, iy - 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawTopCorner = true; } } // check if we need to draw the bottom corner if (!isInside(ix, iy + 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawBottomCorner = true; } } else if (isInside(ix - 1, iy + 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawBottomCorner = true; } } switch (borderMode) { case CENTERED: GraphicsUtils.drawLineVertical(gc, x, y, l, drawTopCorner, drawBottomCorner); break; case EXTERNAL: GraphicsUtils.drawLineVerticalBorderLeft(gc, x, y, l, drawTopCorner, drawBottomCorner); break; case INTERNAL: GraphicsUtils.drawLineVerticalBorderRight(gc, x, y, l, drawTopCorner, drawBottomCorner); break; } } protected void drawLineRight(GC gc, int ix, int iy) { LineValues lineValues = getLineValuesRight(ix, iy); int x = lineValues.x; int y = lineValues.y; int l = lineValues.lenght; boolean drawTopCorner = false; boolean drawBottomCorner = false; BorderModeEnum borderMode = this.borderStyle.getBorderMode(); // check if we need to draw the top corner if (!isInside(ix, iy - 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawTopCorner = true; } } else if (isInside(ix + 1, iy - 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawTopCorner = true; } } // check if we need to draw the bottom corner if (!isInside(ix, iy + 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawBottomCorner = true; } } else if (isInside(ix + 1, iy + 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawBottomCorner = true; } } switch (this.borderStyle.getBorderMode()) { case CENTERED: GraphicsUtils.drawLineVertical(gc, x, y, l, drawTopCorner, drawBottomCorner); break; case EXTERNAL: GraphicsUtils.drawLineVerticalBorderRight(gc, x, y, l, drawTopCorner, drawBottomCorner); break; case INTERNAL: GraphicsUtils.drawLineVerticalBorderLeft(gc, x, y, l, drawTopCorner, drawBottomCorner); break; } } protected void drawLineTop(GC gc, int ix, int iy) { LineValues lineValues = getLineValuesTop(ix, iy); int x = lineValues.x; int y = lineValues.y; int l = lineValues.lenght; boolean drawLeftCorner = false; boolean drawRightCorner = false; BorderModeEnum borderMode = this.borderStyle.getBorderMode(); // check if we need to draw the left corner if (!isInside(ix - 1, iy)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawLeftCorner = true; } } else if (isInside(ix - 1, iy - 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawLeftCorner = true; } } // check if we need to draw the right corner if (!isInside(ix + 1, iy)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawRightCorner = true; } } else if (isInside(ix + 1, iy - 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawRightCorner = true; } } switch (borderMode) { case CENTERED: GraphicsUtils.drawLineHorizontal(gc, x, y, l, drawLeftCorner, drawRightCorner); break; case EXTERNAL: GraphicsUtils.drawLineHorizontalBorderTop(gc, x, y, l, drawLeftCorner, drawRightCorner); break; case INTERNAL: GraphicsUtils.drawLineHorizontalBorderBottom(gc, x, y, l, drawLeftCorner, drawRightCorner); break; } } protected void drawLineBottom(GC gc, int ix, int iy) { LineValues lineValues = getLineValuesBottom(ix, iy); int x = lineValues.x; int y = lineValues.y; int l = lineValues.lenght; boolean drawLeftCorner = false; boolean drawRightCorner = false; BorderModeEnum borderMode = this.borderStyle.getBorderMode(); // check if we need to draw the left corner if (!isInside(ix - 1, iy)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawLeftCorner = true; } } else if (isInside(ix - 1, iy + 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawLeftCorner = true; } } // check if we need to draw the right corner if (!isInside(ix + 1, iy)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.EXTERNAL) { drawRightCorner = true; } } else if (isInside(ix + 1, iy + 1)) { if (borderMode == BorderModeEnum.CENTERED || borderMode == BorderModeEnum.INTERNAL) { drawRightCorner = true; } } switch (borderMode) { case CENTERED: GraphicsUtils.drawLineHorizontal(gc, x, y, l, drawLeftCorner, drawRightCorner); break; case EXTERNAL: GraphicsUtils.drawLineHorizontalBorderBottom(gc, x, y, l, drawLeftCorner, drawRightCorner); break; case INTERNAL: GraphicsUtils.drawLineHorizontalBorderTop(gc, x, y, l, drawLeftCorner, drawRightCorner); break; } } protected boolean isInside(int ix, int iy) { if (!isValid(ix, iy)) { return false; } return this.borderCells[iy][ix].isInsideBorder; } protected boolean isValid(int ix, int iy) { if (iy < 0) { return false; } if (ix < 0) { return false; } if (iy >= this.borderCells.length) { return false; } if (ix >= this.borderCells[iy].length) { return false; } if (this.borderCells[iy][ix] == null) { return false; } if (this.borderCells[iy][ix].bounds == null) { return false; } return true; } }