/*
* 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.actions.shape;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import net.sf.latexdraw.actions.Modifying;
import net.sf.latexdraw.actions.ShapeActionImpl;
import net.sf.latexdraw.models.MathUtils;
import net.sf.latexdraw.models.interfaces.shape.IGroup;
import net.sf.latexdraw.models.interfaces.shape.IPoint;
import net.sf.latexdraw.models.interfaces.shape.IShape;
import net.sf.latexdraw.util.LangTool;
import net.sf.latexdraw.view.jfx.Canvas;
import net.sf.latexdraw.view.jfx.ViewShape;
import org.malai.undo.Undoable;
/**
* This action aligns the provided shapes.
* @author Arnaud Blouin
*/
public class AlignShapes extends ShapeActionImpl<IGroup> implements Undoable, Modifying {
/**
* This enumeration describes the different possible alignment types.
*/
public enum Alignment {
LEFT, RIGHT, TOP, BOTTOM, MID_HORIZ, MID_VERT
}
/** The views corresponding to the shapes to align. */
private List<ViewShape<?>> views;
/** The alignment to perform. */
private Alignment alignment;
/** The former positions of the shapes to align. Used for undoing. */
private List<IPoint> oldPositions;
private Canvas canvas;
public AlignShapes() {
super();
}
@Override
protected void doActionBody() {
views = shape.get().getShapes().stream().map(sh -> canvas.getViewFromShape(sh).get()).collect(Collectors.toList());
oldPositions = shape.get().getShapes().stream().map(sh -> sh.getTopLeftPoint()).collect(Collectors.toList());
redo();
}
/**
* Middle-horizontal aligning the provided shapes.
*/
private void alignMidHoriz() {
double theMaxY = Double.MIN_VALUE;
double theMinY = Double.MAX_VALUE;
final List<Double> middles = new ArrayList<>();
for(final ViewShape<?> view : views) {
final double maxY = view.getBoundsInLocal().getMaxY();
final double minY = view.getBoundsInLocal().getMinY();
if(maxY > theMaxY) theMaxY = maxY;
if(minY < theMinY) theMinY = minY;
middles.add((minY + maxY) / 2d);
}
translateY(middles, (theMaxY + theMinY) / 2d);
}
/**
* Middle-vertical aligning the provided shapes.
*/
private void alignMidVert() {
double theMaxX = Double.MIN_VALUE;
double theMinX = Double.MAX_VALUE;
final List<Double> middles = new ArrayList<>();
for(final ViewShape<?> view : views) {
final double maxX = view.getBoundsInLocal().getMaxX();
final double minX = view.getBoundsInLocal().getMinX();
if(maxX > theMaxX) theMaxX = maxX;
if(minX < theMinX) theMinX = minX;
middles.add((minX + maxX) / 2d);
}
translateX(middles, (theMaxX + theMinX) / 2d);
}
private void translateX(final List<Double> vals, final double ref) {
int i = 0;
for(final IShape sh : shape.get().getShapes()) {
final double middle2 = vals.get(i);
if(!MathUtils.INST.equalsDouble(middle2, ref)) {
sh.translate(ref - middle2, 0d);
}
i++;
}
}
private void translateY(final List<Double> vals, final double ref) {
int i = 0;
for(final IShape sh : shape.get().getShapes()) {
final double y = vals.get(i);
if(!MathUtils.INST.equalsDouble(y, ref)) {
sh.translate(0d, ref - y);
}
i++;
}
}
/**
* Bottom aligning the provided shapes.
*/
private void alignBottom() {
double theMaxY = Double.MIN_VALUE;
final List<Double> ys = new ArrayList<>();
for(final ViewShape<?> view : views) {
final double maxY = view.getBoundsInLocal().getMaxY();
if(maxY > theMaxY) theMaxY = maxY;
ys.add(maxY);
}
translateY(ys, theMaxY);
}
/**
* Top aligning the provided shapes.
*/
private void alignTop() {
double theMinY = Double.MAX_VALUE;
final List<Double> ys = new ArrayList<>();
for(final ViewShape<?> view : views) {
final double minY = view.getBoundsInLocal().getMinY();
if(minY < theMinY) theMinY = minY;
ys.add(minY);
}
translateY(ys, theMinY);
}
/**
* Right aligning the provided shapes.
*/
private void alignRight() {
double theMaxX = Double.MIN_VALUE;
final List<Double> xs = new ArrayList<>();
for(final ViewShape<?> view : views) {
final double maxX = view.getBoundsInLocal().getMaxX();
if(maxX > theMaxX) theMaxX = maxX;
xs.add(maxX);
}
translateX(xs, theMaxX);
}
/**
* Left aligning the provided shapes.
*/
private void alignLeft() {
double theMinX = Double.MAX_VALUE;
final List<Double> xs = new ArrayList<>();
for(final ViewShape<?> view : views) {
final double minX = view.getBoundsInLocal().getMinX();
if(minX < theMinX) theMinX = minX;
xs.add(minX);
}
translateX(xs, theMinX);
}
@Override
public boolean canDo() {
return shape.isPresent() && !shape.get().isEmpty() && canvas != null && alignment != null;
}
@Override
public void undo() {
final IntegerProperty pos = new SimpleIntegerProperty(0);
shape.ifPresent(gp -> {
gp.getShapes().forEach(sh -> {
// Reusing the old position.
final IPoint pt = sh.getTopLeftPoint();
final IPoint oldPt = oldPositions.get(pos.get());
if(!pt.equals(oldPt)) {
sh.translate(oldPt.getX() - pt.getX(), oldPt.getY() - pt.getY());
}
pos.set(pos.get() + 1);
});
gp.setModified(true);
});
}
@Override
public void redo() {
switch(alignment) {
case LEFT:
alignLeft();
break;
case RIGHT:
alignRight();
break;
case TOP:
alignTop();
break;
case BOTTOM:
alignBottom();
break;
case MID_HORIZ:
alignMidHoriz();
break;
case MID_VERT:
alignMidVert();
break;
}
shape.ifPresent(sh -> sh.setModified(true));
}
/**
* Sets the alignment to perform.
*/
public void setAlignment(final Alignment align) {
alignment = align;
}
public void setCanvas(final Canvas theCanvas) {
canvas = theCanvas;
}
@Override
public String getUndoName() {
return LangTool.INSTANCE.getBundle().getString("Actions.30");
}
@Override
public RegistrationPolicy getRegistrationPolicy() {
return RegistrationPolicy.LIMITED;
}
}