001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.command; 003 004import static org.openstreetmap.josm.tools.I18n.trn; 005 006import java.util.Collection; 007import java.util.Objects; 008 009import org.openstreetmap.josm.data.coor.EastNorth; 010import org.openstreetmap.josm.data.osm.Node; 011import org.openstreetmap.josm.data.osm.OsmPrimitive; 012 013public class ScaleCommand extends TransformNodesCommand { 014 /** 015 * Pivot point 016 */ 017 private final EastNorth pivot; 018 019 /** 020 * Current scaling factor applied 021 */ 022 private double scalingFactor; 023 024 /** 025 * World position of the mouse when the user started the command. 026 */ 027 private final EastNorth startEN; 028 029 /** 030 * Creates a ScaleCommand. 031 * Assign the initial object set, compute pivot point. 032 * Computation of pivot point is done by the same rules that are used in 033 * the "align nodes in circle" action. 034 * @param objects objects to fetch nodes from 035 * @param currentEN cuurent eats/north 036 */ 037 public ScaleCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) { 038 super(objects); 039 040 pivot = getNodesCenter(); 041 042 // We remember the very first position of the mouse for this action. 043 // Note that SelectAction will keep the same ScaleCommand when the user 044 // releases the button and presses it again with the same modifiers. 045 // The very first point of this operation is stored here. 046 startEN = currentEN; 047 048 handleEvent(currentEN); 049 } 050 051 /** 052 * Compute new scaling factor and transform nodes accordingly. 053 */ 054 @Override 055 public final void handleEvent(EastNorth currentEN) { 056 double startAngle = Math.atan2(startEN.east()-pivot.east(), startEN.north()-pivot.north()); 057 double endAngle = Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north()); 058 double startDistance = pivot.distance(startEN); 059 double currentDistance = pivot.distance(currentEN); 060 scalingFactor = Math.cos(startAngle-endAngle) * currentDistance / startDistance; 061 transformNodes(); 062 } 063 064 /** 065 * Scale nodes. 066 */ 067 @Override 068 protected void transformNodes() { 069 for (Node n : nodes) { 070 EastNorth oldEastNorth = oldStates.get(n).getEastNorth(); 071 double dx = oldEastNorth.east() - pivot.east(); 072 double dy = oldEastNorth.north() - pivot.north(); 073 double nx = pivot.east() + scalingFactor * dx; 074 double ny = pivot.north() + scalingFactor * dy; 075 n.setEastNorth(new EastNorth(nx, ny)); 076 } 077 } 078 079 @Override 080 public String getDescriptionText() { 081 return trn("Scale {0} node", "Scale {0} nodes", nodes.size(), nodes.size()); 082 } 083 084 @Override 085 public int hashCode() { 086 return Objects.hash(super.hashCode(), pivot, scalingFactor, startEN); 087 } 088 089 @Override 090 public boolean equals(Object obj) { 091 if (this == obj) return true; 092 if (obj == null || getClass() != obj.getClass()) return false; 093 if (!super.equals(obj)) return false; 094 ScaleCommand that = (ScaleCommand) obj; 095 return Double.compare(that.scalingFactor, scalingFactor) == 0 && 096 Objects.equals(pivot, that.pivot) && 097 Objects.equals(startEN, that.startEN); 098 } 099}