/* * 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.OptionalInt; import java.util.stream.Collectors; import java.util.stream.IntStream; 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.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 distributes the provided shapes. * @author Arnaud Blouin */ public class DistributeShapes extends ShapeActionImpl<IGroup> implements Undoable, Modifying { /** * This enumeration describes the different possible alignment types. */ public enum Distribution { VERT_BOT, VERT_TOP, VERT_MID, VERT_EQ, HORIZ_LEFT, HORIZ_RIGHT, HORIZ_MID, HORIZ_EQ; public static boolean isVertical(final Distribution distrib) { return distrib == VERT_BOT || distrib == VERT_TOP || distrib == VERT_MID || distrib == VERT_EQ; } } /** The views corresponding to the shapes to align. */ private List<ViewShape<?>> views; /** The alignment to perform. */ private Distribution distribution; /** The former positions of the shapes to align. Used for undoing. */ private List<IPoint> oldPositions; private Canvas canvas; public DistributeShapes() { 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(); } @Override public boolean canDo() { return shape.isPresent() && !shape.get().isEmpty() && canvas != null && distribution != 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); }); } /** * Distributes at equal distance between the shapes. */ private void distributeEq() { final List<ViewShape<?>> sortedSh = new ArrayList<>(); final List<Double> mins = new ArrayList<>(); final List<Double> maxs = new ArrayList<>(); for(final ViewShape<?> view : views) { final double coord = distribution == Distribution.HORIZ_EQ ? view.getBoundsInLocal().getMinX() : view.getBoundsInLocal().getMinY(); final OptionalInt res = IntStream.range(0, mins.size()).filter(index -> coord < mins.get(index)).findFirst(); if(res.isPresent()) { final int i = res.getAsInt(); sortedSh.add(i, view); mins.add(i, coord); maxs.add(i, distribution == Distribution.HORIZ_EQ ? view.getBoundsInLocal().getMaxX() : view.getBoundsInLocal().getMaxY()); }else { sortedSh.add(view); mins.add(coord); maxs.add(distribution == Distribution.HORIZ_EQ ? view.getBoundsInLocal().getMaxX() : view.getBoundsInLocal().getMaxY()); } } double gap = mins.get(mins.size() - 1) - maxs.get(0); for(int i = 1, size = sortedSh.size() - 1; i < size; i++) { gap -= maxs.get(i) - mins.get(i); } gap /= sortedSh.size() - 1; final double finalGap = gap; if(Distribution.isVertical(distribution)) { IntStream.range(1, sortedSh.size() - 1).forEach(i -> ((IShape) sortedSh.get(i).getUserData()). translate(0d, sortedSh.get(i - 1).getBoundsInLocal().getMaxY() + finalGap - mins.get(i))); }else { IntStream.range(1, sortedSh.size() - 1).forEach(i -> ((IShape) sortedSh.get(i).getUserData()). translate(sortedSh.get(i - 1).getBoundsInLocal().getMaxX() + finalGap - mins.get(i), 0d)); } } /** * Distributes using bottom/top/left/right reference. */ private void distributeNotEq() { final List<ViewShape<?>> sortedSh = new ArrayList<>(); final List<Double> centres = new ArrayList<>(); for(final ViewShape<?> view : views) { double x = 0; switch(distribution) { case HORIZ_LEFT: x = view.getBoundsInLocal().getMinX(); break; case HORIZ_MID: x = (view.getBoundsInLocal().getMinX() + view.getBoundsInLocal().getMaxX()) / 2d; break; case HORIZ_RIGHT: x = view.getBoundsInLocal().getMaxX(); break; case VERT_BOT: x = view.getBoundsInLocal().getMaxY(); break; case VERT_MID: x = (view.getBoundsInLocal().getMinY() + view.getBoundsInLocal().getMaxY()) / 2d; break; case VERT_TOP: x = view.getBoundsInLocal().getMinY(); break; } final double finalX = x; final OptionalInt res = IntStream.range(0, centres.size()).filter(index -> finalX < centres.get(index)).findFirst(); if(res.isPresent()) { final int i = res.getAsInt(); sortedSh.add(i, view); centres.add(i, x); }else { sortedSh.add(view); centres.add(x); } } final double gap = (centres.get(centres.size() - 1) - centres.get(0)) / (views.size() - 1); if(Distribution.isVertical(distribution)) { IntStream.range(1, sortedSh.size() - 1).forEach(i -> ((IShape) sortedSh.get(i).getUserData()). translate(0d, centres.get(0) + i * gap - centres.get(i))); }else { IntStream.range(1, sortedSh.size() - 1).forEach(i -> ((IShape) sortedSh.get(i).getUserData()). translate(centres.get(0) + i * gap - centres.get(i), 0d)); } } @Override public void redo() { if(distribution == Distribution.HORIZ_EQ || distribution == Distribution.VERT_EQ) { distributeEq(); }else { distributeNotEq(); } shape.ifPresent(sh -> sh.setModified(true)); } /** * Sets the alignment to perform. */ public void setDistribution(final Distribution distrib) { distribution = distrib; } public void setCanvas(final Canvas theCanvas) { canvas = theCanvas; } @Override public String getUndoName() { return LangTool.INSTANCE.getBundle().getString("Actions.6"); } @Override public RegistrationPolicy getRegistrationPolicy() { return RegistrationPolicy.LIMITED; } }