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