/* * 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 java.util.List; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import net.sf.latexdraw.models.MathUtils; import net.sf.latexdraw.models.ShapeFactory; import net.sf.latexdraw.models.interfaces.shape.BorderPos; import net.sf.latexdraw.models.interfaces.shape.Color; import net.sf.latexdraw.models.interfaces.shape.FillingStyle; import net.sf.latexdraw.models.interfaces.shape.IPoint; import net.sf.latexdraw.models.interfaces.shape.IShape; import net.sf.latexdraw.models.interfaces.shape.ISingleShape; import net.sf.latexdraw.models.interfaces.shape.LineStyle; import net.sf.latexdraw.models.interfaces.shape.Position; import net.sf.latexdraw.view.pst.PSTricksConstants; import org.eclipse.jdt.annotation.NonNull; import org.malai.mapping.MappingRegistry; /** * The base shape model. * @author Arnaud Blouin */ abstract class LShape implements ISingleShape { /** The thickness of the lines of the shape in pixels. */ protected final @NonNull DoubleProperty thickness; /** The colour of the lines. */ protected final @NonNull ObjectProperty<Color> lineColour; /** The style of the lines. */ protected final @NonNull ObjectProperty<LineStyle> lineStyle; /** The style of the interior of the shape. */ protected final @NonNull ObjectProperty<FillingStyle> fillingStyle; /** The white dash separator for dashed lines in pixel. */ protected final @NonNull DoubleProperty dashSepWhite; /** The black dash separator for dashed lines in pixel. */ protected final @NonNull DoubleProperty dashSepBlack; /** The dot separator for dotted lines. */ protected final @NonNull DoubleProperty dotSep; /** The colour of the interior of the shape. */ protected final @NonNull ObjectProperty<Color> fillingCol; /** The start colour of the gradient. */ protected final @NonNull ObjectProperty<Color> gradColStart; /** The end colour of the gradient. */ protected final @NonNull ObjectProperty<Color> gradColEnd; /** The angle of the gradient in radian. */ protected final @NonNull DoubleProperty gradAngle; /** The middle point of the gradient. */ protected final @NonNull DoubleProperty gradMidPt; /** The separation size between hatchings in pixel. */ protected final @NonNull DoubleProperty hatchingsSep; /** The colour of the hatchings. */ protected final @NonNull ObjectProperty<Color> hatchingsCol; /** The angle of the hatchings in radian. */ protected final @NonNull DoubleProperty hatchingsAngle; /** The thickness of the hatchings in pixel. */ protected final @NonNull DoubleProperty hatchingsWidth; /** The rotation angle of the shape. */ protected final @NonNull DoubleProperty rotationAngle; /** Defines if the points of the shape must be considered. */ protected boolean showPts; /** Defines if the shape has double borders. */ protected final @NonNull BooleanProperty hasDbleBord; /** The colour of the double borders. */ protected final @NonNull ObjectProperty<Color> dbleBordCol; /** The separation size of the double borders in pixel. */ protected final @NonNull DoubleProperty dbleBordSep; /** Defines if the shape has a shadow. */ protected final @NonNull BooleanProperty hasShadow; /** The colour of the shadow. */ protected final @NonNull ObjectProperty<Color> shadowCol; /** The angle of the shadow in radian. */ protected final @NonNull DoubleProperty shadowAngle; /** The size of the shadow in pixel. */ protected final @NonNull DoubleProperty shadowSize; /** The position of the border of the shape. */ protected final @NonNull ObjectProperty<BorderPos> bordersPosition; /** The points of the shape. */ protected final ObservableList<IPoint> points; /** Defined if the shape has been modified. */ protected boolean modified; /** * The second default constructor */ LShape() { super(); modified = false; thickness = new SimpleDoubleProperty(2d); rotationAngle = new SimpleDoubleProperty(0d); shadowAngle = new SimpleDoubleProperty(-Math.PI / 4d); gradAngle = new SimpleDoubleProperty(0d); hatchingsAngle = new SimpleDoubleProperty(0d); hasShadow = new SimpleBooleanProperty(false); hasDbleBord = new SimpleBooleanProperty(false); lineStyle = new SimpleObjectProperty<>(LineStyle.SOLID); lineColour = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_LINE_COLOR); dotSep = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_DOT_STEP * PPC); dashSepBlack = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_DASH_BLACK * PPC); dashSepWhite = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_DASH_WHITE * PPC); hatchingsCol = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_HATCHING_COLOR); hatchingsSep = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_HATCH_SEP * PPC); hatchingsWidth = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_HATCH_WIDTH * PPC); fillingStyle = new SimpleObjectProperty<>(FillingStyle.NONE); fillingCol = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_INTERIOR_COLOR); bordersPosition = new SimpleObjectProperty<>(BorderPos.INTO); dbleBordCol = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_DOUBLE_COLOR); dbleBordSep = new SimpleDoubleProperty(6d); shadowCol = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_SHADOW_COLOR); shadowSize = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_SHADOW_SIZE * PPC); gradColStart = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_GRADIENT_START_COLOR); gradColEnd = new SimpleObjectProperty<>(PSTricksConstants.DEFAULT_GRADIENT_END_COLOR); gradMidPt = new SimpleDoubleProperty(PSTricksConstants.DEFAULT_GRADIENT_MID_POINT); showPts = false; points = FXCollections.observableArrayList(); } @Override public double getFullThickness() { return isDbleBorderable() && hasDbleBord() ? getThickness() * 2d + getDbleBordSep() : getThickness(); } @Override public void copy(final IShape s) { if(s == null) return; setThickness(s.getThickness()); setRotationAngle(s.getRotationAngle()); setShadowAngle(s.getShadowAngle()); setGradAngle(s.getGradAngle()); setHatchingsAngle(s.getHatchingsAngle()); setHasShadow(s.hasShadow()); setHasDbleBord(s.hasDbleBord()); setLineStyle(s.getLineStyle()); setLineColour(s.getLineColour()); setDotSep(s.getDotSep()); setDashSepBlack(s.getDashSepBlack()); setDashSepWhite(s.getDashSepWhite()); setHatchingsCol(s.getHatchingsCol()); setHatchingsSep(s.getHatchingsSep()); setHatchingsWidth(s.getHatchingsWidth()); setFillingStyle(s.getFillingStyle()); setFillingCol(s.getFillingCol()); setBordersPosition(s.getBordersPosition()); setDbleBordCol(s.getDbleBordCol()); setDbleBordSep(s.getDbleBordSep()); setShadowCol(s.getShadowCol()); setShadowSize(s.getShadowSize()); setGradColStart(s.getGradColStart()); setGradColEnd(s.getGradColEnd()); setGradMidPt(s.getGradMidPt()); setShowPts(s.isShowPts()); copyPoints(s); } protected void copyPoints(final IShape sh) { if(sh == null || !getClass().isInstance(sh)) return; points.clear(); sh.getPoints().forEach(pt -> points.add(ShapeFactory.INST.createPoint(pt))); } @Override public void addToRotationAngle(final IPoint gravCentre, final double angle) { if(MathUtils.INST.isValidCoord(angle)) { setRotationAngle(getRotationAngle() + angle); if(gravCentre != null) { final IPoint gravityCentre = getGravityCentre(); final IPoint rotatedGC = gravityCentre.rotatePoint(gravCentre, angle); translate(rotatedGC.getX() - gravityCentre.getX(), rotatedGC.getY() - gravityCentre.getY()); } } } @Override public double getBorderGap() { switch(bordersPosition.get()) { case MID: return hasDbleBord() ? thickness.doubleValue() + getDbleBordSep() / 2d : thickness.doubleValue() / 2d; case OUT: return hasDbleBord() ? thickness.doubleValue() * 2d + getDbleBordSep() : thickness.doubleValue(); case INTO: return 0d; } return 0d; } @Override public @NonNull BorderPos getBordersPosition() { return bordersPosition.get(); } @Override public double getDashSepBlack() { return dashSepBlack.doubleValue(); } @Override public double getDashSepWhite() { return dashSepWhite.doubleValue(); } @Override public Color getDbleBordCol() { return dbleBordCol.get(); } @Override public double getDbleBordSep() { return dbleBordSep.doubleValue(); } @Override public double getDotSep() { return dotSep.doubleValue(); } @Override public Color getFillingCol() { return fillingCol.get(); } @Override public @NonNull FillingStyle getFillingStyle() { return fillingStyle.get(); } @Override public IPoint getFullBottomRightPoint() { final double gap = getBorderGap(); final IPoint br = getBottomRightPoint(); br.translate(gap, gap); return br; } @Override public IPoint getFullTopLeftPoint() { final double gap = getBorderGap(); final IPoint tl = getTopLeftPoint(); tl.translate(-gap, -gap); return tl; } @Override public double getGradAngle() { return gradAngle.doubleValue(); } @Override public Color getGradColEnd() { return gradColEnd.get(); } @Override public Color getGradColStart() { return gradColStart.get(); } @Override public double getGradMidPt() { return gradMidPt.doubleValue(); } @Override public IPoint getGravityCentre() { return points.isEmpty() ? ShapeFactory.INST.createPoint() : getTopLeftPoint().getMiddlePoint(getBottomRightPoint()); } @Override public double getHatchingsAngle() { return hatchingsAngle.doubleValue(); } @Override public Color getHatchingsCol() { return hatchingsCol.get(); } @Override public double getHatchingsSep() { return hatchingsSep.doubleValue(); } @Override public double getHatchingsWidth() { return hatchingsWidth.doubleValue(); } @Override public @NonNull Color getLineColour() { return lineColour.get(); } @Override public @NonNull LineStyle getLineStyle() {// TODO add to the generics but sbt crashes... return lineStyle.get(); } @Override public int getNbPoints() { return points.size(); } @Override public ObservableList<IPoint> getPoints() { return points; } @Override public IPoint getPtAt(final int position) { final IPoint point; if(points.isEmpty() || position < -1 || position >= points.size()) point = null; else point = position == -1 ? points.get(points.size() - 1) : points.get(position); return point; } @Override public double getRotationAngle() { return rotationAngle.get(); } @Override public double getShadowAngle() { return shadowAngle.get(); } @Override public Color getShadowCol() { return shadowCol.get(); } @Override public double getShadowSize() { return shadowSize.get(); } @Override public double getThickness() { return thickness.doubleValue(); } @Override public boolean hasDbleBord() { return hasDbleBord.get(); } @Override public boolean hasGradient() { return isInteriorStylable() && fillingStyle.get() == FillingStyle.GRAD; } @Override public boolean hasHatchings() { return isInteriorStylable() && fillingStyle.get().isHatchings(); } @Override public boolean hasShadow() { return hasShadow.get(); } @Override public boolean isFilled() { return fillingStyle.get().isFilled(); } @Override public boolean isShowPts() { return showPts; } @Override public boolean isColourable() { return true; } @Override public void setBordersPosition(final BorderPos position) { if(position != null && isBordersMovable()) bordersPosition.set(position); } @Override public void setDashSepBlack(final double dash) { if(dash > 0d && MathUtils.INST.isValidCoord(dash)) { dashSepBlack.set(dash); } } @Override public void setDashSepWhite(final double dash) { if(dash > 0d && MathUtils.INST.isValidCoord(dash)) { dashSepWhite.set(dash); } } @Override public void setDbleBordCol(final Color col) { if(col != null && isDbleBorderable()) { dbleBordCol.set(col); } } @Override public void setDbleBordSep(final double sep) { if(sep >= 0 && isDbleBorderable() && MathUtils.INST.isValidCoord(sep)) { dbleBordSep.set(sep); } } @Override public void setDotSep(final double sep) { if(sep >= 0 && MathUtils.INST.isValidCoord(sep)) { dotSep.set(sep); } } @Override public void setFilled(final boolean isFilled) { if(!isFillable()) return; if(isFilled) switch(fillingStyle.get()) { case CLINES: fillingStyle.set(FillingStyle.CLINES_PLAIN); break; case VLINES: fillingStyle.set(FillingStyle.VLINES_PLAIN); break; case HLINES: fillingStyle.set(FillingStyle.HLINES_PLAIN); break; case NONE: fillingStyle.set(FillingStyle.PLAIN); break; case PLAIN: case GRAD: case CLINES_PLAIN: case HLINES_PLAIN: case VLINES_PLAIN: /* Nothing to do. */ break; } else switch(fillingStyle.get()) { case CLINES_PLAIN: fillingStyle.set(FillingStyle.CLINES); break; case VLINES_PLAIN: fillingStyle.set(FillingStyle.VLINES); break; case HLINES_PLAIN: fillingStyle.set(FillingStyle.HLINES); break; case PLAIN: fillingStyle.set(FillingStyle.NONE); break; case NONE: case GRAD: case CLINES: case HLINES: case VLINES: /* Nothing to do. */ break; } } @Override public void setFillingCol(final Color col) { if(col != null && isFillable()) { fillingCol.set(col); } } @Override public void setFillingStyle(final FillingStyle style) { if(style != null && isFillable()) fillingStyle.set(style); } @Override public void setGradAngle(final double angle) { if(MathUtils.INST.isValidCoord(angle) && isInteriorStylable()) { gradAngle.set(angle); } } @Override public void setGradColEnd(final Color col) { if(col != null && isInteriorStylable()) { gradColEnd.set(col); } } @Override public void setGradColStart(final Color col) { if(col != null && isInteriorStylable()) { gradColStart.set(col); } } @Override public void setGradMidPt(final double pt) { if(pt >= 0 && pt <= 1 && isInteriorStylable()) { gradMidPt.set(pt); } } @Override public void setHasDbleBord(final boolean bord) { if(isDbleBorderable()) { hasDbleBord.set(bord); } } @Override public void setHasShadow(final boolean shad) { if(isShadowable()) { hasShadow.set(shad); } } @Override public void setHatchingsAngle(final double angle) { if(MathUtils.INST.isValidCoord(angle) && isInteriorStylable()) { hatchingsAngle.set(angle); } } @Override public void setHatchingsCol(final Color col) { if(col != null && isInteriorStylable()) { hatchingsCol.set(col); } } @Override public void setHatchingsSep(final double sep) { if(MathUtils.INST.isValidCoord(sep) && sep >= 0d && isInteriorStylable()) { hatchingsSep.set(sep); } } @Override public void setHatchingsWidth(final double width) { if(MathUtils.INST.isValidCoord(width) && width > 0d && isInteriorStylable()) { hatchingsWidth.set(width); } } @Override public void setLineColour(final Color col) { if(col != null) { lineColour.set(col); } } @Override public void setLineStyle(final LineStyle style) { if(style != null && isLineStylable()) { lineStyle.setValue(style); } } @Override public void scale(final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) { if(bound == null || pos == null) return; scaleSetPoints(points, prevWidth, prevHeight, pos, bound); } @Override public void scaleWithRatio(final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) { if(bound == null || pos == null) return; scaleSetPointsWithRatio(points, prevWidth, prevHeight, pos, bound); } protected void scaleSetPointsWithRatio(final List<IPoint> pts, final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) { final double s = Math.max(prevWidth / bound.getWidth(), prevHeight / bound.getHeight()); final IPoint refPt = pos.getReferencePoint(bound); final double refX = refPt.getX(); final double refY = refPt.getY(); for(final IPoint pt : pts) { if(!MathUtils.INST.equalsDouble(pt.getX(), refX)) pt.setX(refX + (pt.getX() - refX) * s); if(!MathUtils.INST.equalsDouble(pt.getY(), refY)) pt.setY(refY + (pt.getY() - refY) * s); } } protected void scaleSetPoints(final List<IPoint> pts, final double prevWidth, final double prevHeight, final Position pos, final Rectangle2D bound) { final double sx = prevWidth / bound.getWidth(); final double sy = prevHeight / bound.getHeight(); final boolean xScale = pos.isEast() || pos.isWest(); final boolean yScale = pos.isNorth() || pos.isSouth(); final IPoint refPt = pos.getReferencePoint(bound); final double refX = refPt.getX(); final double refY = refPt.getY(); pts.forEach(pt -> { if(xScale && !MathUtils.INST.equalsDouble(pt.getX(), refX)) pt.setX(refX + (pt.getX() - refX) * sx); if(yScale && !MathUtils.INST.equalsDouble(pt.getY(), refY)) pt.setY(refY + (pt.getY() - refY) * sy); }); } @Override public void setRotationAngle(final double angle) { if(MathUtils.INST.isValidCoord(angle)) { rotationAngle.set(angle); } } @Override public void setShadowAngle(final double angle) { if(isShadowable() && MathUtils.INST.isValidCoord(angle)) { shadowAngle.set(angle); } } @Override public void setShadowCol(final Color col) { if(col != null && isShadowable()) { shadowCol.set(col); } } @Override public void setShadowSize(final double size) { if(isShadowable() && size > 0 && MathUtils.INST.isValidCoord(size)) { shadowSize.set(size); } } @Override public void setShowPts(final boolean pts) { if(isShowPtsable()) showPts = pts; } @Override public void setThickness(final double thick) { if(thick > 0 && isThicknessable() && MathUtils.INST.isValidCoord(thick)) { thickness.setValue(thick); } } @Override public boolean shadowFillsShape() { return true; } @Override public void translate(final double tx, final double ty) { if(MathUtils.INST.isValidPt(tx, ty)) points.forEach(pt -> pt.translate(tx, ty)); } @Override public void mirrorHorizontal(final IPoint origin) { if(MathUtils.INST.isValidPt(origin)) points.forEach(pt -> pt.setPoint(pt.horizontalSymmetry(origin))); } @Override public void mirrorVertical(final IPoint origin) { if(MathUtils.INST.isValidPt(origin)) points.forEach(pt -> pt.setPoint(pt.verticalSymmetry(origin))); } @Override public IPoint getBottomRightPoint() { return points.stream().reduce((p1, p2) -> ShapeFactory.INST.createPoint(Math.max(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()))).orElse(ShapeFactory.INST.createPoint()); } @Override public IPoint getBottomLeftPoint() { return points.stream().reduce((p1, p2) -> ShapeFactory.INST.createPoint(Math.min(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()))).orElse(ShapeFactory.INST.createPoint()); } @Override public IPoint getTopLeftPoint() { return points.stream().reduce((p1, p2) -> ShapeFactory.INST.createPoint(Math.min(p1.getX(), p2.getX()), Math.min(p1.getY(), p2.getY()))).orElse(ShapeFactory.INST.createPoint()); } @Override public IPoint getTopRightPoint() { return points.stream().reduce((p1, p2) -> ShapeFactory.INST.createPoint(Math.max(p1.getX(), p2.getX()), Math.min(p1.getY(), p2.getY()))).orElse(ShapeFactory.INST.createPoint()); } @Override public <T extends IShape> T duplicate() { final T shape = ShapeFactory.INST.newShape((Class<T>)getClass()).get(); shape.copy(this); return shape; } @Override public void setModified(final boolean changed) { if(changed) MappingRegistry.REGISTRY.onObjectModified(this); modified = changed; } @Override public boolean isModified() { return modified; } @Override public void rotate(final IPoint point, final double angle) { final IPoint gc = getGravityCentre(); if(point != null && !gc.equals(point)) {// The position of the shape must be rotated. final IPoint rotGC = gc.rotatePoint(point, angle); translate(rotGC.getX() - gc.getX(), rotGC.getY() - gc.getY()); } setRotationAngle(getRotationAngle() + angle); } @Override public boolean isBordersMovable() { return false; } @Override public boolean isDbleBorderable() { return false; } @Override public boolean isFillable() { return false; } @Override public boolean isInteriorStylable() { return false; } @Override public boolean isLineStylable() { return false; } @Override public boolean isShadowable() { return false; } @Override public boolean isShowPtsable() { return false; } @Override public boolean isThicknessable() { return false; } @Override public boolean isTypeOf(final Class<?> clazz) { return clazz != null && clazz.isInstance(this); } @Override public double getHeight() { return Math.abs(getBottomLeftPoint().getY() - getTopLeftPoint().getY()); } @Override public double getWidth() { return Math.abs(getTopRightPoint().getX() - getTopLeftPoint().getX()); } @Override public @NonNull DoubleProperty thicknessProperty() { return thickness; } @Override public @NonNull ObjectProperty<LineStyle> linestyleProperty() { return lineStyle; } @Override public @NonNull ObjectProperty<BorderPos> borderPosProperty() { return bordersPosition; } @Override public @NonNull ObjectProperty<Color> lineColourProperty() { return lineColour; } @Override public @NonNull ObjectProperty<FillingStyle> fillingProperty() { return fillingStyle; } @Override public @NonNull DoubleProperty dashSepWhiteProperty() { return dashSepWhite; } @Override public @NonNull DoubleProperty dashSepBlackProperty() { return dashSepBlack; } @Override public @NonNull DoubleProperty dotSepProperty() { return dotSep; } @Override public @NonNull BooleanProperty dbleBordProperty() { return hasDbleBord; } @Override public @NonNull DoubleProperty dbleBordSepProperty() { return dbleBordSep; } @Override public @NonNull ObjectProperty<Color> dbleBordColProperty() { return dbleBordCol; } @Override public @NonNull ObjectProperty<Color> gradColStartProperty() { return gradColStart; } @Override public @NonNull ObjectProperty<Color> gradColEndProperty() { return gradColEnd; } @Override public @NonNull ObjectProperty<Color> fillingColProperty() { return fillingCol; } @Override public @NonNull DoubleProperty gradAngleProperty() { return gradAngle; } @Override public @NonNull DoubleProperty gradMidPtProperty() { return gradMidPt; } @Override public @NonNull BooleanProperty shadowProperty() { return hasShadow; } @Override public @NonNull ObjectProperty<Color> shadowColProperty() { return shadowCol; } @Override public @NonNull DoubleProperty shadowAngleProperty() { return shadowAngle; } @Override public @NonNull DoubleProperty shadowSizeProperty() { return shadowSize; } @Override public @NonNull DoubleProperty hatchingsAngleProperty() { return hatchingsAngle; } @Override public @NonNull DoubleProperty hatchingsSepProperty() { return hatchingsSep; } @Override public @NonNull DoubleProperty hatchingsWidthProperty() { return hatchingsWidth; } @Override public @NonNull ObjectProperty<Color> hatchingsColProperty() { return hatchingsCol; } @Override public @NonNull DoubleProperty rotationAngleProperty() { return rotationAngle; } }