/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.util.gui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.Line2D; import java.awt.geom.RoundRectangle2D; import java.util.StringTokenizer; import javax.swing.border.AbstractBorder; import com.servoy.j2db.util.Utils; /** * Special border for advanced bordering * @author jblok */ public class SpecialMatteBorder extends AbstractBorder { private static final int line_join = BasicStroke.JOIN_BEVEL;//JOIN_MITER; private static final int line_cap = BasicStroke.CAP_BUTT; private final float top, left, bottom, right; private final Color topColor, leftColor, bottomColor, rightColor; private float roundingRadius = 0f; private float[] dashPattern; public SpecialMatteBorder(float top, float left, float bottom, float right, Color topColor, Color leftColor, Color bottomColor, Color rightColor) { this.top = top; this.left = left; this.bottom = bottom; this.right = right; this.topColor = topColor; this.leftColor = leftColor; this.bottomColor = bottomColor; this.rightColor = rightColor; } @Override public boolean equals(Object obj) { if (obj instanceof SpecialMatteBorder) { SpecialMatteBorder other = (SpecialMatteBorder)obj; boolean retval = (other.top == top && other.left == left && other.bottom == bottom && other.right == right); retval = retval && Utils.equalObjects(topColor, other.topColor) && Utils.equalObjects(leftColor, other.leftColor) && Utils.equalObjects(bottomColor, other.bottomColor) && Utils.equalObjects(rightColor, other.rightColor) && (roundingRadius == other.roundingRadius); return retval; } else { return false; } } /** * Paints the matte border. */ @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { Color oldColor = g.getColor(); Stroke oldStroke = ((Graphics2D)g).getStroke(); g.translate(x, y); try { if (roundingRadius > 0 && top > 0f && topColor != null) { ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (dashPattern == null) { ((Graphics2D)g).setStroke(new BasicStroke(top, line_cap, line_join)); } else { ((Graphics2D)g).setStroke(new BasicStroke(top, line_cap, line_join, 1f, dashPattern, 0f)); } Shape shape = createRoundedShape(width, height); g.setColor(topColor); ((Graphics2D)g).draw(shape); } // Does not work correctly on macos java 1.5.0_07, left and right lines are shifted 1 pixel to the right...... // else if (top == left && top == bottom && top == right && // topColor != null && topColor.equals(leftColor) && topColor.equals(bottomColor) && topColor.equals(rightColor)) // { // if (top >= 0f) // { // float lineWidth = top; // if (dashPattern == null) // { // ((Graphics2D)g).setStroke(new BasicStroke(lineWidth,line_cap,line_join)); // } // else // { // ((Graphics2D)g).setStroke(new BasicStroke(lineWidth,line_cap,line_join,1f,dashPattern,0f)); // } // // g.setColor(topColor); // float halfLW = lineWidth/2f; // Rectangle2D.Float rect = new Rectangle2D.Float(halfLW, halfLW, width-lineWidth, height-lineWidth); // ((Graphics2D)g).draw(rect); // } // } else { ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); if (top > 0f && topColor != null) { if (dashPattern == null && top == 1f) { ((Graphics2D)g).setStroke(new BasicStroke(top, line_cap, line_join)); g.setColor(topColor); g.drawLine(0, 0, width - 1, 0); } else { float lineWidth = top; float halfLW = lineWidth / 2f; if (dashPattern == null) { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join)); } else { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join, 1f, dashPattern, 0f)); } g.setColor(topColor); Line2D.Float line = new Line2D.Float(0f, halfLW, width, halfLW); ((Graphics2D)g).draw(line); } } if (left > 0f && leftColor != null) { if (dashPattern == null && left == 1f) { ((Graphics2D)g).setStroke(new BasicStroke(left, line_cap, line_join)); g.setColor(leftColor); g.drawLine(0, (int)top, 0, height - 1); } else { float lineWidth = left; float halfLW = lineWidth / 2f; if (dashPattern == null) { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join)); } else { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join, 1f, dashPattern, 0f)); } g.setColor(leftColor); Line2D.Float line = new Line2D.Float(halfLW, top, halfLW, height - bottom); ((Graphics2D)g).draw(line); } } if (bottom > 0f && bottomColor != null) { if (dashPattern == null && bottom == 1f) { ((Graphics2D)g).setStroke(new BasicStroke(bottom, line_cap, line_join)); g.setColor(bottomColor); g.drawLine(0, height - 1, width - 1, height - 1); } else { float lineWidth = bottom; float halfLW = lineWidth / 2f; if (dashPattern == null) { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join)); } else { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join, 1f, dashPattern, 0f)); } g.setColor(bottomColor); Line2D.Float line = new Line2D.Float(0f, height - halfLW, width, height - halfLW); ((Graphics2D)g).draw(line); } } if (right > 0f && rightColor != null) { if (dashPattern == null && right == 1f) { ((Graphics2D)g).setStroke(new BasicStroke(right, line_cap, line_join)); g.setColor(rightColor); g.drawLine(width - 1, (int)top, width - 1, height - 1); } else { float lineWidth = right; float halfLW = lineWidth / 2f; if (dashPattern == null) { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join)); } else { ((Graphics2D)g).setStroke(new BasicStroke(lineWidth, line_cap, line_join, 1f, dashPattern, 0f)); } g.setColor(rightColor); Line2D.Float line = new Line2D.Float(width - halfLW, top, width - halfLW, height - bottom); ((Graphics2D)g).draw(line); } } } } finally { g.translate(-x, -y); g.setColor(oldColor); ((Graphics2D)g).setStroke(oldStroke); } } public Shape createRoundedShape(int width, int height) { float halfLW = top / 2f; float shapeWidth = width - top; float shapeHeight = height - top; // subtract 1px if needed if (2 * (int)halfLW == top) { shapeWidth -= 1; shapeHeight -= 1; } // make this more in line with WC return new RoundRectangle2D.Float(halfLW, halfLW, shapeWidth, shapeHeight, getWebClientCompatibleValue(getArcWidth()), getWebClientCompatibleValue(getArcHeight())); } public Color getTopColor() { return topColor; } public Color getLeftColor() { return leftColor; } public Color getBottomColor() { return bottomColor; } public Color getRightColor() { return rightColor; } public float getBottom() { return bottom; } public float getLeft() { return left; } public float getRight() { return right; } public float getTop() { return top; } @Override public Insets getBorderInsets(Component c, Insets insets) { return applySpaceToInsets(insets, c); } @Override public Insets getBorderInsets(Component c) { Insets retval = new Insets(0, 0, 0, 0); applySpaceToInsets(retval, c); return retval; } private Insets applySpaceToInsets(Insets i, Component c) { // if c is null we are probably in wc float widthInset = (c != null) ? getWebClientCompatibleValue(getArcWidth()) / 8f : 0f; float heightInset = (c != null) ? getWebClientCompatibleValue(getArcHeight()) / 8f : 0f; i.top = (int)Math.ceil(top + heightInset); i.left = (int)Math.ceil(left + widthInset); i.bottom = (int)Math.ceil(bottom + heightInset); i.right = (int)Math.ceil(right + widthInset); return i; } @Override public boolean isBorderOpaque() { return (roundingRadius != 0f); } /** * @return */ public float getRoundingRadius() { return roundingRadius; } /** * @param f */ public void setRoundingRadius(float f) { roundingRadius = f; } /** * @return */ public float[] getDashPattern() { return dashPattern; } /** * @param a */ public void setDashPattern(float[] a) { dashPattern = a; } public static String createDashString(float[] fs) { StringBuffer sb = new StringBuffer(); if (fs != null) { for (int i = 0; i < fs.length; i++) { sb.append(fs[i]); if (i < fs.length - 1) sb.append(";"); //$NON-NLS-1$ } } return sb.toString(); } public static float[] createDash(String dash) { if (dash == null || dash.trim().length() == 0) return null; dash = Utils.stringReplace(dash, ",", ";");//safety //$NON-NLS-1$//$NON-NLS-2$ StringTokenizer tk = new StringTokenizer(dash, ";"); //$NON-NLS-1$ float[] retval = new float[tk.countTokens()]; int i = 0; boolean atLeastOneTokenNotZero = false; while (tk.hasMoreTokens()) { retval[i] = Utils.getAsFloat(tk.nextToken()); if (retval[i] != 0) atLeastOneTokenNotZero = true; i++; } return atLeastOneTokenNotZero ? retval : null; } protected float getArcWidth() { return roundingRadius; } protected float getArcHeight() { return roundingRadius; } private float getWebClientCompatibleValue(float radius) { return Math.max(0, 2 * radius - top); } }