package net.sf.latexdraw.glib.views.Java2D.impl;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.util.Objects;
import net.sf.latexdraw.glib.models.interfaces.shape.IArrow;
import net.sf.latexdraw.glib.models.interfaces.shape.ILine;
import net.sf.latexdraw.glib.models.interfaces.shape.IPoint;
import net.sf.latexdraw.glib.models.interfaces.shape.IShape;
import net.sf.latexdraw.glib.models.interfaces.shape.IArrow.ArrowStyle;
import net.sf.latexdraw.glib.views.Java2D.interfaces.IViewArrow;
import net.sf.latexdraw.util.LNumber;
/**
* Defines a view of an arrow.<br>
* <br>
* This file is part of LaTeXDraw.<br>
* Copyright (c) 2005-2014 Arnaud BLOUIN<br>
* <br>
* LaTeXDraw is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later version.
* <br>
* LaTeXDraw is distributed without any warranty; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.<br>
* <br>
* 08/03/2011<br>
* @author Arnaud BLOUIN
* @version 3.0
* @since 3.0
*/
class LArrowView implements IViewArrow {
/** The arrow model to view. */
protected IArrow model;
/** The path of the view. */
protected Path2D path;
/**
* Creates the view of the arrow.
* @param model The model of the arrow.
* @throws IllegalArgumentException If the given arrow is null.
* @since 3.0
*/
protected LArrowView(final IArrow model) {
super();
this.model = Objects.requireNonNull(model);
path = new Path2D.Double();
}
@Override
public void paint(final Graphics2D g, final Color fColour, final boolean asShadow) {
if(!model.hasStyle()) return ;
final ILine arrowLine = model.getArrowLine();
if(arrowLine==null) return;
final IPoint pt1 = arrowLine.getPoint1();
final double lineAngle = arrowLine.getLineAngle();
final double lineB = arrowLine.getB();
final double c2x;
final double c2y;
final double c3x;
final double c3y;
if(LNumber.equalsDouble(Math.abs(lineAngle), Math.PI/2.)) {
final double cx = pt1.getX();
final double cy = pt1.getY();
c2x = Math.cos(lineAngle)*cx - Math.sin(lineAngle)*cy;
c2y = Math.sin(lineAngle)*cx + Math.cos(lineAngle)*cy;
c3x = Math.cos(-lineAngle)*(cx-c2x) - Math.sin(-lineAngle)*(cy-c2y);
c3y = Math.sin(-lineAngle)*(cx-c2x) + Math.cos(-lineAngle)*(cy-c2y);
}
else {
c2x = -Math.sin(lineAngle)*lineB;
c2y = Math.cos(lineAngle)*lineB;
c3x = Math.cos(-lineAngle)*-c2x - Math.sin(-lineAngle)*(lineB-c2y);
c3y = Math.sin(-lineAngle)*-c2x + Math.cos(-lineAngle)*(lineB-c2y);
}
if(!LNumber.equalsDouble(lineAngle%(Math.PI*2.),0.)) {
g.rotate(lineAngle);
g.translate(c3x,c3y);
}
paintArrow(g, fColour, asShadow);
if(!LNumber.equalsDouble(lineAngle%(Math.PI*2.),0.)) {
g.translate(-c3x,-c3y);
g.rotate(-lineAngle);
}
}
protected void paintCircle(final Graphics2D g, final Color fillColour, final Color lineColour) {
g.setColor(fillColour);
g.fill(path);
g.setColor(lineColour);
g.draw(path);
}
protected void paintDisk(final Graphics2D g, final Color lineColour) {
g.setColor(lineColour);
g.setStroke(new BasicStroke((float)model.getShape().getThickness(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
g.fill(path);
g.draw(path);
}
protected void paintRoundBracket(final Graphics2D g, final Color lineColor) {
g.setColor(lineColor);
g.setStroke(new BasicStroke((float)model.getShape().getThickness(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
g.draw(path);
}
protected void paintBarBracket(final Graphics2D g, final Color lineColor) {
g.setStroke(new BasicStroke((float)model.getShape().getFullThickness(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
g.setColor(lineColor);
g.draw(path);
}
protected void paintArrow(final Graphics2D g, final Color lineColor) {
g.setColor(lineColor);
g.fill(path);
}
protected void paintArrow(final Graphics2D g, final Color fColour, final boolean asShadow) {
final IShape shape = model.getShape();
final Color lineColor = asShadow ? shape.getShadowCol() : shape.getLineColour();
final Color fillColor = asShadow ? shape.getShadowCol() : fColour;
switch(model.getArrowStyle()) {
case LEFT_DBLE_ARROW :
case RIGHT_DBLE_ARROW :
case RIGHT_ARROW :
case LEFT_ARROW : paintArrow(g, lineColor); break;
case CIRCLE_END :
case CIRCLE_IN : paintCircle(g, fillColor, lineColor); break;
case DISK_END :
case DISK_IN : paintDisk(g, lineColor); break;
case LEFT_ROUND_BRACKET :
case RIGHT_ROUND_BRACKET : paintRoundBracket(g, lineColor); break;
case BAR_END :
case BAR_IN :
case LEFT_SQUARE_BRACKET :
case RIGHT_SQUARE_BRACKET : paintBarBracket(g, lineColor); break;
case ROUND_IN :
case ROUND_END :
g.setColor(lineColor);
g.setStroke(new BasicStroke((float)model.getShape().getFullThickness(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
g.draw(path);
break;
case SQUARE_END :
g.setColor(lineColor);
g.setStroke(new BasicStroke((float)model.getShape().getFullThickness(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
g.draw(path);
break;
case NONE: break;
}
}
protected void updatePathDiskCircleEnd(final double xRot, final double yRot) {
final double lineWidth = model.getShape().getFullThickness();
final double arrowRadius = model.getRoundShapedArrowRadius();
LEllipseView.setEllipsePath(path, xRot - arrowRadius+lineWidth/2., yRot - arrowRadius+lineWidth/2., arrowRadius*2.-lineWidth, arrowRadius*2.-lineWidth);
}
protected void updatePathDiskCircleIn(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
final double arrowRadius = model.getRoundShapedArrowRadius();
final double lineWidth = model.getShape().getFullThickness();
double x = xRot+lineWidth/2.;
if(!isArrowInPositiveDirection(pt1, pt2))
x -=2.*arrowRadius;
LEllipseView.setEllipsePath(path, x, yRot-arrowRadius+lineWidth/2., arrowRadius*2.-lineWidth, arrowRadius*2.-lineWidth);
}
protected void updatePathRightLeftSquaredBracket(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
final boolean invert = model.isInverted();
final double[] xs = new double[2];
final double[] ys = new double[2];
final double lineWidth = model.getShape().getFullThickness();
double lgth = model.getBracketShapedArrowLength()+model.getShape().getFullThickness()/2.;
if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert))
lgth *= -1.;
updatePathBarIn(xRot, yRot, pt1, pt2, xs, ys);
final double x3 = xs[0]+lgth;
final double x4 = xs[1]+lgth;
path.moveTo(xs[0], ys[0]+lineWidth/2.);
path.lineTo(x3, ys[0]+lineWidth/2.);
path.moveTo(xs[1], ys[1]-lineWidth/2.);
path.lineTo(x4, ys[1]-lineWidth/2.);
}
protected void updatePathBarIn(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2, final double[] xs, final double[] ys) {
final double width = model.getBarShapedArrowWidth();
final double lineWidth = model.getShape().getThickness();
final double dec = isArrowInPositiveDirection(pt1, pt2) ? lineWidth/2. : -lineWidth/2.;
xs[0] = xRot+dec;
xs[1] = xRot+dec;
ys[0] = yRot-width/2.;
ys[1] = yRot+width/2.;
path.moveTo(xs[0], ys[0]);
path.lineTo(xs[1], ys[1]);
}
protected void updatePathBarEnd(final double xRot, final double yRot) {
final double width = model.getBarShapedArrowWidth();
path.moveTo(xRot, yRot-width/2.);
path.lineTo(xRot, yRot+width/2.);
}
private void updatePathArrow(final double x1, final double y1, final double x2, final double y2,
final double x3, final double y3, final double x4, final double y4) {
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.lineTo(x4, y4);
path.closePath();
}
protected void updatePathRightLeftArrow(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
final boolean invert= model.isInverted();
final double width = model.getArrowShapedWidth();
double length = model.getArrowLength()*width;
double inset = model.getArrowInset()*length;
double x = xRot;
if(invert)
x += isArrowInPositiveDirection(pt1, pt2) ? length : -length;
if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) {
length *= -1.;
inset *= -1.;
}
updatePathArrow(x, yRot, x+length, yRot-width/2., x+length-inset, yRot, x+length, yRot+width/2.);
}
private boolean isArrowInPositiveDirection(final IPoint pt1, final IPoint pt2) {
return pt1.getX()<pt2.getX() || pt1.getX()==pt2.getX() && pt1.getY()<pt2.getY();
}
protected void updatePathRoundLeftRightBracket(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
final boolean invert = model.isInverted();
final double width = model.getBarShapedArrowWidth();
final double lgth = model.getRBracketNum()*width;
final double xarc = model.isInverted() ? xRot : xRot+model.getShape().getThickness()/2.;
final double widtharc = lgth*2. + (invert ? model.getShape().getThickness()/2. : 0.);
Shape s = new Arc2D.Double(xarc, yRot-width/2., widtharc, width, 130, 100, Arc2D.OPEN);
if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) {
final double rotX = Math.cos(Math.PI)*xRot - Math.sin(Math.PI)*yRot;
final double rotY = Math.sin(Math.PI)*xRot + Math.cos(Math.PI)*yRot;
final AffineTransform at = AffineTransform.getTranslateInstance(xRot-rotX, yRot-rotY);
at.rotate(Math.PI);
s = at.createTransformedShape(s);
}
path.append(s, false);
}
protected void updatePathDoubleLeftRightArrow(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
final boolean invert= model.isInverted();
final double width = model.getArrowShapedWidth();
double length = model.getArrowLength()*width;
double inset = model.getArrowInset()*length;
double x = xRot;
if(invert)
x += isArrowInPositiveDirection(pt1, pt2) ? 2.*length : -2.*length;
if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) {
length *= -1.;
inset *= -1.;
}
updatePathArrow(x, yRot, x+length, yRot-width/2., x+length-inset, yRot, x+length, yRot+width/2.);
updatePathArrow(x+length, yRot, x+2.*length, yRot-width/2., x+2.*length-inset, yRot, x+2.*length, yRot+width/2.);
final double x2 = x+length-inset;
final double x2bis = x+2.*length-inset;
path.lineTo(x2, yRot);
path.moveTo(x2bis, yRot);
}
protected void updatePathSquareRoundEnd(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
path.lineTo(pt1.getX()<pt2.getX() ? xRot+1. : xRot-1., yRot);
path.moveTo(xRot, yRot);
}
protected void updatePathRoundIn(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) {
final double lineWidth = isArrowInPositiveDirection(pt1, pt2) ? model.getShape().getFullThickness() : -model.getShape().getFullThickness();
final double x = xRot+lineWidth/2.;
path.moveTo(x, yRot);
path.lineTo(x, yRot);
}
/**
* Updates the path of the arrow.
* @since 3.0
*/
@Override
public void updatePath() {
path.reset();
final ILine arrowLine = model.getArrowLine();
if(model.getArrowStyle()==ArrowStyle.NONE || arrowLine==null) return;
final double xRot;
final double yRot;
final double lineAngle = arrowLine.getLineAngle();
final IPoint pt1 = arrowLine.getPoint1();
final IPoint pt2 = arrowLine.getPoint2();
final double lineB = arrowLine.getB();
if(LNumber.equalsDouble(Math.abs(lineAngle), Math.PI/2.) || LNumber.equalsDouble(Math.abs(lineAngle), 0.)) {
xRot = pt1.getX();
yRot = pt1.getY();
} else {
xRot = Math.cos(-lineAngle)*pt1.getX()-Math.sin(-lineAngle)*(pt1.getY()-lineB);
yRot = Math.sin(-lineAngle)*pt1.getX()+Math.cos(-lineAngle)*(pt1.getY()-lineB)+lineB;
}
switch(model.getArrowStyle()) {
case BAR_END : updatePathBarEnd(xRot, yRot); break;
case BAR_IN : updatePathBarIn(xRot, yRot, pt1, pt2, new double[2], new double[2]); break;
case CIRCLE_END :
case DISK_END : updatePathDiskCircleEnd(xRot, yRot); break;
case CIRCLE_IN :
case DISK_IN : updatePathDiskCircleIn(xRot, yRot, pt1, pt2); break;
case RIGHT_ARROW :
case LEFT_ARROW : updatePathRightLeftArrow(xRot, yRot, pt1, pt2); break;
case RIGHT_DBLE_ARROW :
case LEFT_DBLE_ARROW : updatePathDoubleLeftRightArrow(xRot, yRot, pt1, pt2); break;
case RIGHT_ROUND_BRACKET:
case LEFT_ROUND_BRACKET : updatePathRoundLeftRightBracket(xRot, yRot, pt1, pt2); break;
case LEFT_SQUARE_BRACKET:
case RIGHT_SQUARE_BRACKET:updatePathRightLeftSquaredBracket(xRot, yRot, pt1, pt2); break;
case SQUARE_END :
case ROUND_END : updatePathSquareRoundEnd(xRot, yRot, pt1, pt2); break;
case ROUND_IN : updatePathRoundIn(xRot, yRot, pt1, pt2); break;
case NONE : break;
}
}
}