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<? extends 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 setScalingFactor(Math.cos(startAngle-endAngle) * currentDistance / startDistance); 061 transformNodes(); 062 } 063 064 /** 065 * Set the scaling factor 066 * @param scalingFactor The scaling factor. 067 */ 068 protected void setScalingFactor(double scalingFactor) { 069 this.scalingFactor = scalingFactor; 070 } 071 072 /** 073 * Scale nodes. 074 */ 075 @Override 076 protected void transformNodes() { 077 for (Node n : nodes) { 078 EastNorth oldEastNorth = oldStates.get(n).getEastNorth(); 079 double dx = oldEastNorth.east() - pivot.east(); 080 double dy = oldEastNorth.north() - pivot.north(); 081 double nx = pivot.east() + scalingFactor * dx; 082 double ny = pivot.north() + scalingFactor * dy; 083 n.setEastNorth(new EastNorth(nx, ny)); 084 } 085 } 086 087 @Override 088 public String getDescriptionText() { 089 return trn("Scale {0} node", "Scale {0} nodes", nodes.size(), nodes.size()); 090 } 091 092 @Override 093 public int hashCode() { 094 return Objects.hash(super.hashCode(), pivot, scalingFactor, startEN); 095 } 096 097 @Override 098 public boolean equals(Object obj) { 099 if (this == obj) return true; 100 if (obj == null || getClass() != obj.getClass()) return false; 101 if (!super.equals(obj)) return false; 102 ScaleCommand that = (ScaleCommand) obj; 103 return Double.compare(that.scalingFactor, scalingFactor) == 0 && 104 Objects.equals(pivot, that.pivot) && 105 Objects.equals(startEN, that.startEN); 106 } 107}