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