/*
* 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.instruments;
import com.google.inject.Inject;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.collections.ListChangeListener;
import javafx.fxml.Initializable;
import javafx.geometry.Point3D;
import javafx.scene.Cursor;
import javafx.scene.Node;
import net.sf.latexdraw.actions.shape.ModifyShapeProperty;
import net.sf.latexdraw.actions.shape.MoveCtrlPoint;
import net.sf.latexdraw.actions.shape.MovePointShape;
import net.sf.latexdraw.actions.shape.RotateShapes;
import net.sf.latexdraw.actions.shape.ScaleShapes;
import net.sf.latexdraw.actions.shape.ShapeProperties;
import net.sf.latexdraw.handlers.ArcAngleHandler;
import net.sf.latexdraw.handlers.CtrlPointHandler;
import net.sf.latexdraw.handlers.Handler;
import net.sf.latexdraw.handlers.MovePtHandler;
import net.sf.latexdraw.handlers.RotationHandler;
import net.sf.latexdraw.handlers.ScaleHandler;
import net.sf.latexdraw.models.MathUtils;
import net.sf.latexdraw.models.ShapeFactory;
import net.sf.latexdraw.models.interfaces.shape.IArc;
import net.sf.latexdraw.models.interfaces.shape.IBezierCurve;
import net.sf.latexdraw.models.interfaces.shape.IControlPointShape;
import net.sf.latexdraw.models.interfaces.shape.IDrawing;
import net.sf.latexdraw.models.interfaces.shape.IGroup;
import net.sf.latexdraw.models.interfaces.shape.IModifiablePointsShape;
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 org.malai.action.Action;
import org.malai.javafx.binding.JfXWidgetBinding;
import org.malai.javafx.interaction.library.DnD;
/**
* This instrument manages the selected views.
* @author Arnaud BLOUIN
*/
public class Border extends CanvasInstrument implements Initializable {
/** The handlers that scale shapes. */
private final List<ScaleHandler> scaleHandlers;
/** The handlers that move points. */
private final List<MovePtHandler> mvPtHandlers;
/** The handlers that move first control points. */
private final List<CtrlPointHandler> ctrlPt1Handlers;
/** The handlers that move second control points. */
private final List<CtrlPointHandler> ctrlPt2Handlers;
/** The handler that sets the start angle of an arc. */
private final ArcAngleHandler arcHandlerStart;
/** The handler that sets the end angle of an arc. */
private final ArcAngleHandler arcHandlerEnd;
/** The handler that rotates shapes. */
private RotationHandler rotHandler;
private DnD2MovePoint movePointInteractor;
private DnD2MoveCtrlPoint moveCtrlPtInteractor;
@Inject private MetaShapeCustomiser metaCustomiser;
Border() {
super();
mvPtHandlers = new ArrayList<>();
ctrlPt1Handlers = new ArrayList<>();
ctrlPt2Handlers = new ArrayList<>();
arcHandlerStart = new ArcAngleHandler(true);
arcHandlerEnd = new ArcAngleHandler(false);
scaleHandlers = new ArrayList<>(8);
}
@Override
public void initialize(final URL location, final ResourceBundle resources) {
scaleHandlers.add(new ScaleHandler(Position.NW, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.NORTH, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.NE, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.WEST, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.EAST, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.SW, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.SOUTH, canvas.getSelectionBorder()));
scaleHandlers.add(new ScaleHandler(Position.SE, canvas.getSelectionBorder()));
rotHandler = new RotationHandler(canvas.getSelectionBorder());
scaleHandlers.forEach(handler -> canvas.addToWidgetLayer(handler));
canvas.addToWidgetLayer(rotHandler);
canvas.addToWidgetLayer(arcHandlerStart);
canvas.addToWidgetLayer(arcHandlerEnd);
canvas.getDrawing().getSelection().getShapes().addListener(
(ListChangeListener.Change<? extends IShape> evt) -> setActivated(!canvas.getDrawing().getSelection().isEmpty()));
setActivated(false);
}
@Override
public void setActivated(final boolean activated) {
super.setActivated(activated);
scaleHandlers.forEach(handler -> handler.setVisible(activated));
rotHandler.setVisible(activated);
if(activated) {
updatePointsHandlers();
}else {
mvPtHandlers.forEach(handler -> handler.setVisible(false));
ctrlPt1Handlers.forEach(handler -> handler.setVisible(false));
ctrlPt2Handlers.forEach(handler -> handler.setVisible(false));
arcHandlerStart.setVisible(false);
arcHandlerEnd.setVisible(false);
}
}
@Override
public void interimFeedback() {
canvas.setCursor(Cursor.DEFAULT);
}
@Override
public void onActionDone(final Action action) {
if(action instanceof MoveCtrlPoint || action instanceof MovePointShape || action instanceof ScaleShapes) {
metaCustomiser.dimPosCustomiser.update();
}
}
private void updatePointsHandlers() {
final IGroup selection = canvas.getDrawing().getSelection();
if(selection.size() == 1) {
final IShape sh = selection.getShapeAt(0);
updateMvPtHandlers(sh);
updateCtrlPtHandlers(sh);
updateArcHandlers(sh);
}
}
private void updateArcHandlers(final IShape selectedShape) {
if(selectedShape instanceof IArc) {
final IArc arc = (IArc) selectedShape;
arcHandlerStart.setCurrentArc(arc);
arcHandlerEnd.setCurrentArc(arc);
arcHandlerStart.setVisible(true);
arcHandlerEnd.setVisible(true);
}else {
arcHandlerStart.setVisible(false);
arcHandlerEnd.setVisible(false);
}
}
private void updateMvPtHandlers(final IShape selectedShape) {
if(selectedShape instanceof IModifiablePointsShape) {
initialisePointHandler(mvPtHandlers, pt -> new MovePtHandler(pt), selectedShape.getPoints());
movePointInteractor.getInteraction().registerToNodes(mvPtHandlers.stream().map(h -> (Node)h).collect(Collectors.toList()));
}
}
private void updateCtrlPtHandlers(final IShape selectedShape) {
if(selectedShape instanceof IBezierCurve) {
final IBezierCurve pts = (IBezierCurve) selectedShape;
initialisePointHandler(ctrlPt1Handlers, pt -> new CtrlPointHandler(pt), pts.getFirstCtrlPts());
initialisePointHandler(ctrlPt2Handlers, pt -> new CtrlPointHandler(pt), pts.getSecondCtrlPts());
moveCtrlPtInteractor.getInteraction().registerToNodes(
Stream.concat(ctrlPt1Handlers.stream(), ctrlPt2Handlers.stream()).map(h -> (Node)h).collect(Collectors.toList()));
}
}
private <T extends Node & Handler> void initialisePointHandler(final List<T> handlers, final Function<IPoint, T> supplier, final List<IPoint> pts) {
handlers.forEach(handler -> {
canvas.removeFromWidgetLayer(handler);
handler.flush();
});
handlers.clear();
pts.forEach(pt -> {
final T handler = supplier.apply(pt);
canvas.addToWidgetLayer(handler);
handlers.add(handler);
});
}
@Override
protected void configureBindings() throws InstantiationException, IllegalAccessException {
movePointInteractor = new DnD2MovePoint(this);
moveCtrlPtInteractor = new DnD2MoveCtrlPoint(this);
addBinding(new DnD2Scale(this));
addBinding(movePointInteractor);
addBinding(moveCtrlPtInteractor);
addBinding(new DnD2Rotate(this));
addBinding(new DnD2ArcAngle(this));
}
private static class DnD2MovePoint extends JfXWidgetBinding<MovePointShape, DnD, Border> {
DnD2MovePoint(final Border ins) throws IllegalAccessException, InstantiationException {
super(ins, true, MovePointShape.class, DnD.class);
}
@Override
public void initAction() {
final IGroup group = instrument.canvas.getDrawing().getSelection();
if(group.size() == 1 && group.getShapeAt(0) instanceof IModifiablePointsShape) {
final MovePtHandler handler = (MovePtHandler) interaction.getSrcObject().get();
action.setPoint(handler.getPoint());
action.setShape((IModifiablePointsShape) group.getShapeAt(0));
}
}
@Override
public void updateAction() {
super.updateAction();
final Node node = interaction.getSrcObject().get();
final Point3D startPt = node.localToParent(interaction.getSrcPoint().get());
final Point3D endPt = node.localToParent(interaction.getEndPt().get());
final IPoint ptToMove = ((MovePtHandler) node).getPoint();
final double x = ptToMove.getX() + endPt.getX() - startPt.getX();
final double y = ptToMove.getY() + endPt.getY() - startPt.getY();
action.setNewCoord(instrument.grid.getTransformedPointToGrid(new Point3D(x, y, 0d)));
}
@Override
public boolean isConditionRespected() {
return interaction.getSrcPoint().isPresent() && interaction.getEndPt().isPresent() && interaction.getSrcObject().isPresent() &&
interaction.getSrcObject().get() instanceof MovePtHandler;
}
}
private static class DnD2MoveCtrlPoint extends JfXWidgetBinding<MoveCtrlPoint, DnD, Border> {
DnD2MoveCtrlPoint(final Border ins) throws IllegalAccessException, InstantiationException {
super(ins, true, MoveCtrlPoint.class, DnD.class);
}
@Override
public void initAction() {
final IGroup group = instrument.canvas.getDrawing().getSelection();
if(group.size() == 1 && group.getShapeAt(0) instanceof IControlPointShape) {
final CtrlPointHandler handler = (CtrlPointHandler) interaction.getSrcObject().get();
action.setPoint(handler.getPoint());
action.setShape((IControlPointShape) group.getShapeAt(0));
action.setIsFirstCtrlPt(instrument.ctrlPt1Handlers.contains(interaction.getSrcObject().get()));
}
}
@Override
public void updateAction() {
super.updateAction();
final Node node = interaction.getSrcObject().get();
final Point3D startPt = node.localToParent(interaction.getSrcPoint().get());
final Point3D endPt = node.localToParent(interaction.getEndPt().get());
final IPoint ptToMove = ((CtrlPointHandler) node).getPoint();
final double x = ptToMove.getX() + endPt.getX() - startPt.getX();
final double y = ptToMove.getY() + endPt.getY() - startPt.getY();
action.setNewCoord(instrument.grid.getTransformedPointToGrid(new Point3D(x, y, 0d)));
}
@Override
public boolean isConditionRespected() {
return interaction.getSrcPoint().isPresent() && interaction.getEndPt().isPresent() && interaction.getSrcObject().isPresent()
&& interaction.getSrcObject().get() instanceof CtrlPointHandler;
}
}
private static class DnD2ArcAngle extends JfXWidgetBinding<ModifyShapeProperty, DnD, Border> {
/** The gravity centre used for the rotation. */
private IPoint gc;
/** Defines whether the current handled shape is rotated. */
private boolean isRotated;
/** The current handled shape. */
private IArc shape;
private IPoint gap;
DnD2ArcAngle(final Border ins) throws IllegalAccessException, InstantiationException {
super(ins, true, ModifyShapeProperty.class, DnD.class, ins.arcHandlerStart, ins.arcHandlerEnd);
gap = ShapeFactory.INST.createPoint();
isRotated = false;
}
@Override
public void initAction() {
final IDrawing drawing = instrument.canvas.getDrawing();
if(drawing.getSelection().size() == 1) {
shape = (IArc) drawing.getSelection().getShapeAt(0);
final double rotAngle = shape.getRotationAngle();
IPoint pt = ShapeFactory.INST.createPoint(interaction.getSrcObject().get().localToParent(interaction.getSrcPoint().get()));
gc = shape.getGravityCentre();
IPoint pCentre;
if(interaction.getSrcObject().get() == instrument.arcHandlerStart) {
action.setProperty(ShapeProperties.ARC_START_ANGLE);
pCentre = shape.getStartPoint();
}else {
action.setProperty(ShapeProperties.ARC_END_ANGLE);
pCentre = shape.getEndPoint();
}
if(MathUtils.INST.equalsDouble(rotAngle, 0d)) {
isRotated = false;
}else {
pt = pt.rotatePoint(gc, -rotAngle);
pCentre = pCentre.rotatePoint(gc, -rotAngle);
isRotated = true;
}
gap.setPoint(pt.getX() - pCentre.getX(), pt.getY() - pCentre.getY());
action.setGroup(drawing.getSelection().duplicateDeep(false));
}
}
@Override
public void updateAction() {
IPoint pt = ShapeFactory.INST.createPoint(interaction.getSrcObject().get().localToParent(interaction.getEndPt().get()));
if(isRotated) {
pt = pt.rotatePoint(gc, -shape.getRotationAngle());
}
gap = ShapeFactory.INST.createPoint();
action.setValue(computeAngle(ShapeFactory.INST.createPoint(pt.getX() - gap.getX(), pt.getY() - gap.getY())));
}
private double computeAngle(final IPoint position) {
final double angle = Math.acos((position.getX() - gc.getX()) / position.distance(gc));
return position.getY() > gc.getY() ? 2d * Math.PI - angle : angle;
}
@Override
public boolean isConditionRespected() {
return interaction.getSrcObject().isPresent() && interaction.getSrcPoint().isPresent() && interaction.getEndPt().isPresent();
}
}
private static class DnD2Scale extends JfXWidgetBinding<ScaleShapes, DnD, Border> {
/** The point corresponding to the 'press' position. */
private IPoint p1;
/** The x gap (gap between the pressed position and the targeted position) of the X-scaling. */
private double xGap;
/** The y gap (gap between the pressed position and the targeted position) of the Y-scaling. */
private double yGap;
DnD2Scale(final Border ins) throws IllegalAccessException, InstantiationException {
super(ins, true, ScaleShapes.class, DnD.class, ins.scaleHandlers.stream().map(h -> (Node)h).collect(Collectors.toList()));
}
private void setXGap(final Position refPosition, final IPoint tl, final IPoint br) {
switch(refPosition) {
case NW:
case SW:
case WEST:
xGap = p1.getX() - br.getX();
break;
case NE:
case SE:
case EAST:
xGap = tl.getX() - p1.getX();
break;
default:
xGap = 0d;
}
}
private void setYGap(final Position refPosition, final IPoint tl, final IPoint br) {
switch(refPosition) {
case NW:
case NE:
case NORTH:
yGap = p1.getY() - br.getY();
break;
case SW:
case SE:
case SOUTH:
yGap = tl.getY() - p1.getY();
break;
default:
yGap = 0d;
}
}
@Override
public void initAction() {
final IDrawing drawing = instrument.canvas.getDrawing();
final ScaleHandler handler = (ScaleHandler) getInteraction().getSrcObject().get();
final Position refPosition = handler.getPosition().getOpposite();
final IPoint br = drawing.getSelection().getBottomRightPoint();
final IPoint tl = drawing.getSelection().getTopLeftPoint();
p1 = ShapeFactory.INST.createPoint(interaction.getSrcObject().get().localToParent(interaction.getSrcPoint().get()));
setXGap(refPosition, tl, br);
setYGap(refPosition, tl, br);
action.setDrawing(drawing);
action.setShape(drawing.getSelection().duplicateDeep(false));
action.setRefPosition(refPosition);
}
@Override
public void updateAction() {
super.updateAction();
final IPoint pt = ShapeFactory.INST.createPoint(interaction.getSrcObject().get().localToParent(interaction.getEndPt().get()));
final Position refPosition = action.getRefPosition().get();
if(refPosition.isSouth()) {
action.setNewY(pt.getY() + yGap);
}else if(refPosition.isNorth()) {
action.setNewY(pt.getY() - yGap);
}
if(refPosition.isWest()) {
action.setNewX(pt.getX() - xGap);
}else if(refPosition.isEast()) {
action.setNewX(pt.getX() + xGap);
}
}
@Override
public boolean isConditionRespected() {
return interaction.getSrcObject().isPresent() && interaction.getSrcPoint().isPresent() && interaction.getEndPt().isPresent();
}
@Override
public void interimFeedback() {
super.interimFeedback();
action.getRefPosition().ifPresent(pos -> {
switch(pos) {
case EAST:
instrument.canvas.setCursor(Cursor.W_RESIZE);
break;
case NE:
instrument.canvas.setCursor(Cursor.SW_RESIZE);
break;
case NORTH:
instrument.canvas.setCursor(Cursor.S_RESIZE);
break;
case NW:
instrument.canvas.setCursor(Cursor.SE_RESIZE);
break;
case SE:
instrument.canvas.setCursor(Cursor.NW_RESIZE);
break;
case SOUTH:
instrument.canvas.setCursor(Cursor.N_RESIZE);
break;
case SW:
instrument.canvas.setCursor(Cursor.NE_RESIZE);
break;
case WEST:
instrument.canvas.setCursor(Cursor.E_RESIZE);
break;
}
});
}
}
private static class DnD2Rotate extends JfXWidgetBinding<RotateShapes, DnD, Border> {
/** The point corresponding to the 'press' position. */
private IPoint p1;
/** The gravity centre used for the rotation. */
private IPoint gc;
DnD2Rotate(final Border ins) throws IllegalAccessException, InstantiationException {
super(ins, true, RotateShapes.class, DnD.class, ins.rotHandler);
}
@Override
public void initAction() {
final IDrawing drawing = instrument.canvas.getDrawing();
p1 = ShapeFactory.INST.createPoint(interaction.getSrcObject().get().localToParent(interaction.getSrcPoint().get()));
gc = drawing.getSelection().getGravityCentre();
action.setGravityCentre(gc);
action.setShape(drawing.getSelection().duplicateDeep(false));
}
@Override
public void updateAction() {
action.setRotationAngle(gc.computeRotationAngle(p1,
ShapeFactory.INST.createPoint(interaction.getSrcObject().get().localToParent(interaction.getEndPt().get()))));
}
@Override
public boolean isConditionRespected() {
return interaction.getSrcObject().isPresent() && interaction.getSrcPoint().isPresent() && interaction.getEndPt().isPresent();
}
}
}