/*
* This file is part of LaTeXDraw.
* Copyright (c) 2005-2017 Arnaud BLOUIN
* 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.
* 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.
*/
package net.sf.latexdraw.models.impl;
import java.awt.geom.Rectangle2D;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import net.sf.latexdraw.models.MathUtils;
import net.sf.latexdraw.models.ShapeFactory;
import net.sf.latexdraw.models.interfaces.prop.IDotProp;
import net.sf.latexdraw.models.interfaces.shape.Color;
import net.sf.latexdraw.models.interfaces.shape.DotStyle;
import net.sf.latexdraw.models.interfaces.shape.IDot;
import net.sf.latexdraw.models.interfaces.shape.IPoint;
import net.sf.latexdraw.models.interfaces.shape.IShape;
import net.sf.latexdraw.models.interfaces.shape.Position;
import net.sf.latexdraw.view.latex.DviPsColors;
/**
* An implementation of a dot.
* @author Arnaud Blouin
*/
class LDot extends LPositionShape implements IDot {
/** The current style of the dot. */
private final ObjectProperty<DotStyle> style;
/** The radius of the dot. */
private final DoubleProperty diametre;
/**
* @param pt The centre of the dot.
*/
LDot(final IPoint pt) {
super(pt);
style = new SimpleObjectProperty<>(DotStyle.DOT);
diametre = new SimpleDoubleProperty(40d);
}
@Override
public Color getFillingCol() {
return isFillable() ? super.getFillingCol() : DviPsColors.BLACK;
}
@Override
public DotStyle getDotStyle() {
return style.get();
}
@Override
public void setDotStyle(final DotStyle dotStyle) {
if(dotStyle != null) {
style.set(dotStyle);
}
}
@Override
public double getDiametre() {
return diametre.get();
}
@Override
public void setDiametre(final double diam) {
if(diam > 0d && MathUtils.INST.isValidCoord(diam)) {
diametre.set(diam);
}
}
/**
* Returns the radius computed using a new position (value) and an axe (isX).
* @param value The new X or Y coordinate of the extremity of the dot.
* @param isX True: the value will be considered on the X-axe. Otherwise, on the Y-axe.
* @return The new radius.
*/
protected double getNewRadius(final double value, final boolean isX) {
if(MathUtils.INST.isValidCoord(value)) {
return isX ? Math.abs(getPosition().getX() - value) : Math.abs(getPosition().getY() - value);
}
return Double.NaN;
}
@Override
public void mirrorHorizontal(final IPoint origin) {
setPosition(getPosition().horizontalSymmetry(origin));
}
@Override
public void mirrorVertical(final IPoint origin) {
setPosition(getPosition().verticalSymmetry(origin));
}
@Override
public double getX() {
return points.get(0).getX();
}
@Override
public void setX(final double x) {
points.get(0).setX(x);
}
@Override
public double getY() {
return points.get(0).getY();
}
@Override
public void setY(final double y) {
points.get(0).setY(y);
}
@Override
public void scale(final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) {
scaleWithRatio(prevWidth, prevHeight, pos, bound);
}
@Override
public void scaleWithRatio(final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) {
setDiametre(getDiametre() * Math.max(prevWidth / bound.getWidth(), prevHeight / bound.getHeight()));
}
@Override
public IPoint getPosition() {
// The position of the dot is its centre.
return points.get(0);
}
@Override
public void copy(final IShape sh) {
super.copy(sh);
if(sh != null && sh.isTypeOf(IDotProp.class)) {
final IDotProp dot = (IDotProp) sh;
setDotStyle(dot.getDotStyle());
setDiametre(dot.getDiametre());
setDotFillingCol(dot.getDotFillingCol());
}
}
@Override
public IPoint getBottomLeftPoint() {
final IPoint tl = ShapeFactory.INST.createPoint();
final IPoint br = ShapeFactory.INST.createPoint();
getTopLeftBottomRightPoints(tl, br);
return ShapeFactory.INST.createPoint(tl.getX(), br.getY());
}
@Override
public IPoint getBottomRightPoint() {
final IPoint br = ShapeFactory.INST.createPoint();
getTopLeftBottomRightPoints(ShapeFactory.INST.createPoint(), br);
return br;
}
@Override
public IPoint getTopLeftPoint() {
final IPoint tl = ShapeFactory.INST.createPoint();
getTopLeftBottomRightPoints(tl, ShapeFactory.INST.createPoint());
return tl;
}
@Override
public IPoint getTopRightPoint() {
final IPoint tl = ShapeFactory.INST.createPoint();
final IPoint br = ShapeFactory.INST.createPoint();
getTopLeftBottomRightPoints(tl, br);
return ShapeFactory.INST.createPoint(br.getX(), tl.getY());
}
/**
* Gives the top-left point and the bottom-right point of the dot
* considering its current style.
* @param tl The top-left point to set. Must not be null.
* @param br The bottom-right point to set. Must not be null.
* @throws NullPointerException If tl or br is null.
* @since 3d
*/
protected void getTopLeftBottomRightPoints(final IPoint tl, final IPoint br) {
final IPoint centre = getPosition();
final double x = centre.getX();
final double y = centre.getY();
final double diam = getDiametre();
final double tlx = x - diam;
final double tly = y - diam;
final double brx = x + diam;
final double bry = y + diam;
final double dec = 2d * diam / THICKNESS_O_STYLE_FACTOR;
// Each dot shape has a special shape computed from the parameters
// defined below.
switch(getDotStyle()) {
case ASTERISK:// TODO: to check, I do not think it works.
final double radiusAst = tly + diam / 5d - (bry - diam / 5d) / 2d + dec;
tl.setX(Math.cos(7d * Math.PI / 6d) * radiusAst + x);
tl.setY(tly + diam / 5d - dec);
br.setX(Math.cos(Math.PI / 6d) * radiusAst + x);
br.setY(bry - diam / 5d + dec);
break;
case BAR:
// The thickness of the bar.
final double barThickness = diam / 8d;
tl.setX(x - barThickness);
tl.setY(tly);
br.setX(x + barThickness);
// TODO: check if it is not radius*(1/1.875+1/8.): the bar
// thickness may be used into radius/1.875
br.setY(bry + diam / 1.875);
break;
case DIAMOND:
case FDIAMOND:
final double p = 2d * Math.abs(tlx - brx) / (2d * Math.sin(GOLDEN_ANGLE)) * Math.cos(GOLDEN_ANGLE);
final double x1 = brx - 1.5 * dec;
final double x2 = tlx + 1.5 * dec;
tl.setX(x1 < x2 ? x1 : x2);
tl.setY((tly + bry) / 2d + p / 2d - 1.5 * dec);
br.setX(x1 > x2 ? x1 : x2);
br.setY((tly + bry) / 2d - p / 2d + 1.5 * dec);
break;
case FPENTAGON:
case PENTAGON:
final double dist = diam + dec;
final double xValue = Math.sin(2d * Math.PI / 5d) * dist;
tl.setX(-xValue + x);
tl.setY(tly - dec);
br.setX(xValue + x);
br.setY(0.25 * (Math.sqrt(5d) + 1d) * dist + y + dec);
break;
case FSQUARE:
case SQUARE:// TODO may be wrong, to compare with 2d.
tl.setX(tlx);
tl.setY(tly);
br.setX(brx);
br.setY(bry);
break;
case FTRIANGLE:
case TRIANGLE:
tl.setX(tlx - 0.3 * dec);
tl.setY(tly - 1.5 * dec);
br.setX(brx + 0.3 * dec);
br.setY(bry - 3d * dec);
break;
case DOT:
case O:
case OPLUS:
case OTIMES:
tl.setX(tlx);
tl.setY(tly);
br.setX(brx);
br.setY(bry);
break;
case PLUS:// TODO may be wrong, to compare with 2d.
final double plusGap = diam / 80d;
tl.setX(tlx - plusGap);
tl.setY(tly - plusGap);
br.setX(brx + plusGap);
br.setY(bry + plusGap);
break;
case X:// TODO may be wrong, to compare with 2d.
final double crossGap = diam / 5d;
tl.setX(tlx - crossGap);
tl.setY(tly - crossGap);
br.setX(brx + crossGap);
br.setY(bry + crossGap);
break;
}
}
@Override
public boolean isFillable() {
return getDotStyle().isFillable();
}
@Override
public boolean isFilled() {
final DotStyle dotStyle = getDotStyle();
return isFillable() || dotStyle == DotStyle.FDIAMOND || dotStyle == DotStyle.FPENTAGON || dotStyle == DotStyle.FSQUARE ||
dotStyle == DotStyle.FTRIANGLE || dotStyle == DotStyle.DOT;
}
@Override
public IPoint getLazyTopLeftPoint() {
final IPoint centre = getPosition();
final double diam = getDiametre();
return ShapeFactory.INST.createPoint(centre.getX() - diam / 2d, centre.getY() - diam / 2d);
}
@Override
public IPoint getLazyBottomRightPoint() {
final IPoint centre = getPosition();
final double diam = getDiametre();
return ShapeFactory.INST.createPoint(centre.getX() + diam / 2d, centre.getY() + diam / 2d);
}
@Override
public double getPlusGap() {
return getDiametre() / 160d;
}
@Override
public double getCrossGap() {
return getDiametre() / 10d;
}
@Override
public double getBarGap() {
return getDiametre() / 3.75;
}
@Override
public double getBarThickness() {
return getDiametre() / 8d;
}
@Override
public double getGeneralGap() {
return getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR;
}
@Override
public double getOGap() {
final double dec = getDotStyle() == DotStyle.O ? 3.6 : 2.6;
return getDiametre() * (0.1 / dec) * 2d;
}
@Override
public Color getDotFillingCol() {
return getFillingCol();
}
@Override
public void setDotFillingCol(final Color value) {
setFillingCol(value);
}
@Override
public ObjectProperty<DotStyle> styleProperty() {
return style;
}
@Override
public DoubleProperty diametreProperty() {
return diametre;
}
}