Index: core/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
===================================================================
--- core/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(revision 3659)
+++ core/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java	(working copy)
@@ -26,6 +26,7 @@
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.MoveCommand;
 import org.openstreetmap.josm.command.RotateCommand;
+import org.openstreetmap.josm.command.ScaleCommand;
 import org.openstreetmap.josm.command.SequenceCommand;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -60,7 +61,7 @@
 public class SelectAction extends MapMode implements SelectionEnded {
     //static private final Logger logger = Logger.getLogger(SelectAction.class.getName());
 
-    enum Mode { move, rotate, select }
+    enum Mode { move, rotate, scale, select }
     private Mode mode = null;
     private SelectionManager selectionManager;
 
@@ -78,11 +79,16 @@
     private Cursor oldCursor;
 
     /**
-     * The position of the mouse before the user moves a node.
+     * The position of the mouse before the user starts to drag it while pressing a button.
      */
-    private Point mousePos;
+    private Point startingDraggingPos;
 
     /**
+     * The last known position of the mouse.
+     */
+    private Point lastMousePos;
+
+    /**
      * The time of the user mouse down event.
      */
     private long mouseDownTime = 0;
@@ -105,7 +111,7 @@
      * @param mapFrame The MapFrame this action belongs to.
      */
     public SelectAction(MapFrame mapFrame) {
-        super(tr("Select"), "move/move", tr("Select, move and rotate objects"),
+        super(tr("Select"), "move/move", tr("Select, move, scale and rotate objects"),
                 Shortcut.registerShortcut("mapmode:select", tr("Mode: {0}", tr("Select")), KeyEvent.VK_S, Shortcut.GROUP_EDIT),
                 mapFrame,
                 getCursor("normal", "selection", Cursor.DEFAULT_CURSOR));
@@ -169,7 +175,7 @@
         // do not count anything as a move if it lasts less than 100 milliseconds.
         if ((mode == Mode.move) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return;
 
-        if(mode != Mode.rotate) // button is pressed in rotate mode
+        if(mode != Mode.rotate && mode != Mode.scale) // button is pressed in rotate mode
             if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
                 return;
 
@@ -177,18 +183,22 @@
             setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
         }
 
+        if ( startingDraggingPos == null )
+        	startingDraggingPos = new Point(e.getX(), e.getY());
+        
         if (!initialMoveThresholdExceeded) {
-            int dxp = mousePos.x - e.getX();
-            int dyp = mousePos.y - e.getY();
+            int dxp = lastMousePos.x - e.getX();
+            int dyp = lastMousePos.y - e.getY();
             int dp = (int) Math.sqrt(dxp*dxp+dyp*dyp);
             if (dp < initialMoveThreshold) return;
             initialMoveThresholdExceeded = true;
         }
 
-        EastNorth mouseEN = mv.getEastNorth(e.getX(), e.getY());
-        EastNorth mouseStartEN = mv.getEastNorth(mousePos.x, mousePos.y);
-        double dx = mouseEN.east() - mouseStartEN.east();
-        double dy = mouseEN.north() - mouseStartEN.north();
+        EastNorth currentEN = mv.getEastNorth(e.getX(), e.getY());
+        EastNorth lastEN = mv.getEastNorth(lastMousePos.x, lastMousePos.y);
+        EastNorth startEN = mv.getEastNorth(startingDraggingPos.x, startingDraggingPos.y);
+        double dx = currentEN.east() - lastEN.east();
+        double dy = currentEN.north() - lastEN.north();
         if (dx == 0 && dy == 0)
             return;
 
@@ -210,13 +220,13 @@
             virtualWays.clear();
             virtualNode = null;
         } else {
-            // Currently we support moving and rotating, which do not affect relations.
+            // Currently we support only transformations which do not affect relations.
             // So don't add them in the first place to make handling easier
             Collection<OsmPrimitive> selection = getCurrentDataSet().getSelectedNodesAndWays();
             Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
 
-            // when rotating, having only one node makes no sense - quit silently
-            if (mode == Mode.rotate && affectedNodes.size() < 2)
+            // for these transformations, having only one node makes no sense - quit silently
+            if (affectedNodes.size() < 2 && ( mode == Mode.rotate || mode == Mode.scale ) )
                 return;
 
             Command c = !Main.main.undoRedo.commands.isEmpty()
@@ -250,16 +260,23 @@
                     }
                 }
             } else if (mode == Mode.rotate) {
-                if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).getRotatedNodes())) {
-                    ((RotateCommand)c).rotateAgain(mouseStartEN, mouseEN);
+                if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).getTransformedNodes())) {
+                    ((RotateCommand)c).handleEvent(currentEN);
                 } else {
-                    Main.main.undoRedo.add(new RotateCommand(selection, mouseStartEN, mouseEN));
+                    Main.main.undoRedo.add(new RotateCommand(selection, currentEN));
                 }
+            } else if (mode == Mode.scale) {
+                if (c instanceof ScaleCommand && affectedNodes.equals(((ScaleCommand)c).getTransformedNodes())) {
+                    ((ScaleCommand)c).handleEvent(currentEN);
+                } else {
+                    Main.main.undoRedo.add(new ScaleCommand(selection, currentEN));
+                }
             }
         }
 
         mv.repaint();
-        mousePos = e.getPoint();
+        if ( mode != Mode.scale )
+        lastMousePos = e.getPoint();
 
         didMouseDrag = true;
     }
@@ -267,7 +284,7 @@
     @Override public void mouseMoved(MouseEvent e) {
         // Mac OSX simulates with  ctrl + mouse 1  the second mouse button hence no dragging events get fired.
         //
-        if ((Main.platform instanceof PlatformHookOsx) && mode == Mode.rotate) {
+        if ((Main.platform instanceof PlatformHookOsx) && (mode == Mode.rotate || mode == Mode.scale) ) {
             mouseDragged(e);
         }
     }
@@ -418,6 +435,7 @@
 
         boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
         boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
+        boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
 
         // We don't want to change to draw tool if the user tries to (de)select
         // stuff but accidentally clicks in an empty area when selection is empty
@@ -425,7 +443,7 @@
         didMouseDrag = false;
         initialMoveThresholdExceeded = false;
         mouseDownTime = System.currentTimeMillis();
-        mousePos = e.getPoint();
+        lastMousePos = e.getPoint();
 
         Collection<OsmPrimitive> c = MapView.asColl(
                 mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, false));
@@ -442,6 +460,19 @@
             // Mode.rotate redraws here
             setCursor(ImageProvider.getCursor("rotate", null));
             mv.repaint();
+        } else if (alt && ctrl) {
+                mode = Mode.scale;
+
+                if (getCurrentDataSet().getSelected().isEmpty()) {
+                    getCurrentDataSet().setSelected(c);
+                }
+
+                // Mode.select redraws when selectPrims is called
+                // Mode.move   redraws when mouseDragged is called
+                // Mode.scale redraws here
+                // TODO: mouse cursor for scaling
+                setCursor(ImageProvider.getCursor("rotate", null));
+                mv.repaint();
         } else if (!c.isEmpty()) {
             mode = Mode.move;
 
@@ -468,6 +499,8 @@
         if(!mv.isActiveLayerVisible())
             return;
 
+        startingDraggingPos = null;
+
         restoreCursor();
         if (mode == Mode.select) {
             selectionManager.unregister(mv);
@@ -487,7 +520,7 @@
 
                 // do nothing if the click was to short to be recognized as a drag,
                 // but the release position is farther than 10px away from the press position
-                if (mousePos.distanceSq(e.getPoint())<100) {
+                if (lastMousePos.distanceSq(e.getPoint())<100) {
                     selectPrims(cyclePrims(cycleList, e), e, true, false);
 
                     // If the user double-clicked a node, change to draw mode
@@ -678,8 +711,10 @@
             return tr("Release the mouse button to stop moving. Ctrl to merge with nearest node.");
         else if (mode == Mode.rotate)
             return tr("Release the mouse button to stop rotating.");
+        else if (mode == Mode.scale)
+            return tr("Release the mouse button to stop scaling.");
         else
-            return tr("Move objects by dragging; Shift to add to selection (Ctrl to toggle); Shift-Ctrl to rotate selected; or change selection");
+            return tr("Move objects by dragging; Shift to add to selection (Ctrl to toggle); Shift-Ctrl to rotate selected; Alt-Ctrl to scale selected; or change selection");
     }
 
     @Override public boolean layerIsSupported(Layer l) {
Index: core/src/org/openstreetmap/josm/command/TransformNodesCommand.java
===================================================================
--- core/src/org/openstreetmap/josm/command/TransformNodesCommand.java	(revision 0)
+++ core/src/org/openstreetmap/josm/command/TransformNodesCommand.java	(revision 0)
@@ -0,0 +1,150 @@
+package org.openstreetmap.josm.command;
+
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Point;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.swing.JLabel;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Abstract class with common services for nodes rotation and scaling commands.
+ *
+ * @author Olivier Croquette <ocroquette@free.fr>
+ */
+public abstract class TransformNodesCommand extends Command {
+
+	/**
+	 * The nodes to transform.
+	 */
+	protected Collection<Node> nodes = new LinkedList<Node>();
+
+	/**
+	 * Small helper for holding the interesting part of the old data state of the
+	 * nodes.
+	 */
+	public static class OldState {
+		LatLon latlon;
+		EastNorth eastNorth;
+		boolean modified;
+	}
+
+	/**
+	 * List of all old states of the nodes.
+	 */
+	protected Map<Node, OldState> oldStates = new HashMap<Node, OldState>();
+
+	/**
+	 * Stores the state of the nodes before the command.
+	 */
+	protected void storeOldState() {
+		for (Node n : this.nodes) {
+			OldState os = new OldState();
+			os.latlon = new LatLon(n.getCoor());
+			os.eastNorth = n.getEastNorth();
+			os.modified = n.isModified();
+			oldStates.put(n, os);
+		}
+	}
+
+
+	/**
+	 * Creates a TransformNodesObject.
+	 * Find out the impacted nodes and store their initial state.
+	 */
+	public TransformNodesCommand(Collection<OsmPrimitive> objects) {
+		this.nodes = AllNodesVisitor.getAllNodes(objects);
+		storeOldState();
+	}
+
+	/**
+	 * Handling of a mouse event (e.g. dragging event). 
+     * @param currentEN the current world position of the mouse
+	 */
+	public abstract void handleEvent(EastNorth currentEN);
+
+	/**
+	 * Implementation for the nodes transformation.
+	 * No parameters are given here, you should handle the user input in handleEvent()
+	 * and store it internally.
+	 */
+	protected abstract void transformNodes();
+	
+	/**
+	 * Finally apply the transformation of the nodes.
+	 * This is called when the user is happy with the current state of the command 
+	 * and its effects. 
+	 */
+	@Override
+	public boolean executeCommand() {
+		transformNodes();
+		flagNodesAsModified();
+		return true;
+	}
+
+	/**
+	 * Flag all nodes as modified.
+	 */
+	public void flagNodesAsModified() {
+		for (Node n : nodes) {
+			n.setModified(true);
+		}
+	}
+
+	/**
+	 * Restore the state of the nodes from the backup.
+	 */
+	@Override
+	public void undoCommand() {
+		for (Node n : nodes) {
+			OldState os = oldStates.get(n);
+			n.setCoor(os.latlon);
+			n.setModified(os.modified);
+		}
+	}
+
+	@Override
+	public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+		for (OsmPrimitive osm : nodes) {
+			modified.add(osm);
+		}
+	}
+
+	@Override
+	public JLabel getDescription() {
+		return new JLabel(trn("Transform {0} node", "Transform {0} nodes", nodes.size(), nodes.size()), ImageProvider.get("data", "node"), JLabel.HORIZONTAL);
+	}
+
+	/**
+	 * Get the nodes with the current transformation applied.
+	 */
+	public Collection<Node> getTransformedNodes() {
+		return nodes;
+	}
+
+	/**
+	 * Get the center of the nodes under modification.
+	 * It's just the barycenter.
+	 */
+	public EastNorth getNodesCenter() {
+		EastNorth sum = new EastNorth(0,0);
+
+		for (Node n : nodes ) {
+			EastNorth en = n.getEastNorth();
+			sum = sum.add(en.east(), en.north());
+		}
+		return new EastNorth(sum.east()/this.nodes.size(), sum.north()/this.nodes.size());
+
+	}
+}
Index: core/src/org/openstreetmap/josm/command/ScaleCommand.java
===================================================================
--- core/src/org/openstreetmap/josm/command/ScaleCommand.java	(revision 0)
+++ core/src/org/openstreetmap/josm/command/ScaleCommand.java	(revision 0)
@@ -0,0 +1,85 @@
+package org.openstreetmap.josm.command;
+
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.util.Collection;
+
+import javax.swing.JLabel;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+public class ScaleCommand extends TransformNodesCommand {
+	/**
+	 * Pivot point
+	 */
+	private EastNorth pivot;
+
+	/**
+	 * Current scaling factor applied
+	 */
+	private double scalingFactor;
+
+	/**
+	 * World position of the mouse when the user started the command.
+	 * 
+	 */
+	EastNorth startEN = null;
+	
+	/**
+	 * Creates a ScaleCommand.
+	 * Assign the initial object set, compute pivot point.
+	 * Computation of pivot point is done by the same rules that are used in
+	 * the "align nodes in circle" action.
+	 */
+	public ScaleCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
+		super(objects);
+
+		pivot = getNodesCenter();
+
+		// We remember the very first position of the mouse for this action.
+		// Note that SelectAction will keep the same ScaleCommand when the user
+		// releases the button and presses it again with the same modifiers.
+		// The very first point of this operation is stored here.
+		startEN   = currentEN;
+
+		handleEvent(currentEN);
+	}
+
+	/**
+	 * Compute new scaling factor and transform nodes accordingly.
+	 */
+	@Override
+	public void handleEvent(EastNorth currentEN) {
+		double startAngle = Math.atan2(startEN.east()-pivot.east(), startEN.north()-pivot.north());
+		double endAngle = Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
+		double startDistance = pivot.distance(startEN);
+		double currentDistance = pivot.distance(currentEN);
+		scalingFactor = Math.cos(startAngle-endAngle) * currentDistance / startDistance;
+		transformNodes();
+	}
+
+
+	/**
+	 * Scale nodes.
+	 */
+	@Override
+	protected void transformNodes() {
+		// scalingFactor = 2.0;
+		for (Node n : nodes) {
+			EastNorth oldEastNorth = oldStates.get(n).eastNorth;
+			double dx = oldEastNorth.east() - pivot.east();
+			double dy = oldEastNorth.north() - pivot.north();
+			double nx = pivot.east() + scalingFactor * dx;
+			double ny = pivot.north() + scalingFactor * dy;
+			n.setEastNorth(new EastNorth(nx, ny));
+		}
+	}
+
+	@Override public JLabel getDescription() {
+		return new JLabel(trn("Scale {0} node", "Scale {0} nodes", nodes.size(), nodes.size()), ImageProvider.get("data", "node"), JLabel.HORIZONTAL);
+	}
+
+}
Index: core/src/org/openstreetmap/josm/command/RotateCommand.java
===================================================================
--- core/src/org/openstreetmap/josm/command/RotateCommand.java	(revision 3659)
+++ core/src/org/openstreetmap/josm/command/RotateCommand.java	(working copy)
@@ -19,127 +19,90 @@
 /**
  * RotateCommand rotates a number of objects around their centre.
  *
- * @author Frederik Ramm <frederik@remote.org>
  */
-public class RotateCommand extends Command {
+public class RotateCommand extends TransformNodesCommand {
 
     /**
-     * The objects to rotate.
+     * Pivot point
      */
-    private Collection<Node> nodes = new LinkedList<Node>();
-
-    /**
-     * pivot point
-     */
     private EastNorth pivot;
 
-    /**
-     * Small helper for holding the interesting part of the old data state of the
-     * objects.
-     */
-    public static class OldState {
-        LatLon latlon;
-        EastNorth eastNorth;
-        boolean modified;
-    }
+	/**
+	 * World position of the mouse when the user started the command.
+	 * 
+	 */
+	EastNorth startEN = null;
 
     /**
      * angle of rotation starting click to pivot
      */
-    private double startAngle;
+    private double startAngle = 0.0;
 
     /**
      * computed rotation angle between starting click and current mouse pos
      */
-    private double rotationAngle;
+    private double rotationAngle = 0.0;
 
     /**
-     * List of all old states of the objects.
-     */
-    private Map<Node, OldState> oldState = new HashMap<Node, OldState>();
-
-    /**
      * Creates a RotateCommand.
-     * Assign the initial object set, compute pivot point and rotation angle.
-     * Computation of pivot point is done by the same rules that are used in
-     * the "align nodes in circle" action.
+     * Assign the initial object set, compute pivot point and inital rotation angle.
      */
-    public RotateCommand(Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) {
+    public RotateCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) {
+		super(objects);
 
-        this.nodes = AllNodesVisitor.getAllNodes(objects);
-        pivot = new EastNorth(0,0);
+		pivot = getNodesCenter();
 
-        for (Node n : this.nodes) {
-            OldState os = new OldState();
-            os.latlon = new LatLon(n.getCoor());
-            os.eastNorth = n.getEastNorth();
-            os.modified = n.isModified();
-            oldState.put(n, os);
-            pivot = pivot.add(os.eastNorth.east(), os.eastNorth.north());
-        }
-        pivot = new EastNorth(pivot.east()/this.nodes.size(), pivot.north()/this.nodes.size());
+		// We remember the very first position of the mouse for this action.
+		// Note that SelectAction will keep the same ScaleCommand when the user
+		// releases the button and presses it again with the same modifiers.
+		// The very first point of this operation is stored here.
+		startEN   = currentEN;
 
-        rotationAngle = Math.PI/2;
-        rotateAgain(start, end);
-    }
+		startAngle = getAngle(currentEN);
+        rotationAngle = 0.0;
 
+		handleEvent(currentEN);
+	}
+    
     /**
-     * Rotate the same set of objects again, by the angle between given
-     * start and end nodes. Internally this is added to the existing
-     * rotation so a later undo will undo the whole rotation.
-     */
-    public void rotateAgain(EastNorth start, EastNorth end) {
-        // compute angle
-        startAngle = Math.atan2(start.east()-pivot.east(), start.north()-pivot.north());
-        double endAngle = Math.atan2(end.east()-pivot.east(), end.north()-pivot.north());
-        rotationAngle += startAngle - endAngle;
-        rotateNodes(false);
+     * Get angle between the horizontal axis and the line formed by the pivot and give points.
+     **/
+    protected double getAngle(EastNorth currentEN) {
+    	if ( pivot == null )
+    		return 0.0; // should never happen by contract
+        return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
     }
 
-    /**
-     * Helper for actually rotationg the nodes.
-     * @param setModified - true if rotated nodes should be flagged "modified"
-     */
-    private void rotateNodes(boolean setModified) {
+	/**
+	 * Compute new rotation angle and transform nodes accordingly.
+	 */
+    @Override
+    public void handleEvent(EastNorth currentEN) {
+    	double currentAngle = getAngle(currentEN);
+    	rotationAngle = currentAngle - startAngle;
+    	transformNodes();
+    }
+
+	/**
+	 * Rotate nodes.
+	 */
+    @Override
+    protected void transformNodes() {
         for (Node n : nodes) {
             double cosPhi = Math.cos(rotationAngle);
             double sinPhi = Math.sin(rotationAngle);
-            EastNorth oldEastNorth = oldState.get(n).eastNorth;
+            EastNorth oldEastNorth = oldStates.get(n).eastNorth;
             double x = oldEastNorth.east() - pivot.east();
             double y = oldEastNorth.north() - pivot.north();
-            double nx =  sinPhi * x + cosPhi * y + pivot.east();
-            double ny = -cosPhi * x + sinPhi * y + pivot.north();
+            double nx =  cosPhi * x + sinPhi * y + pivot.east();
+            double ny = -sinPhi * x + cosPhi * y + pivot.north();
             n.setEastNorth(new EastNorth(nx, ny));
-            if (setModified) {
-                n.setModified(true);
-            }
         }
     }
 
-    @Override public boolean executeCommand() {
-        rotateNodes(true);
-        return true;
-    }
-
-    @Override public void undoCommand() {
-        for (Node n : nodes) {
-            OldState os = oldState.get(n);
-            n.setCoor(os.latlon);
-            n.setModified(os.modified);
-        }
-    }
-
-    @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
-        for (OsmPrimitive osm : nodes) {
-            modified.add(osm);
-        }
-    }
-
-    @Override public JLabel getDescription() {
+    @Override
+    public JLabel getDescription() {
         return new JLabel(trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size()), ImageProvider.get("data", "node"), JLabel.HORIZONTAL);
     }
 
-    public Collection<Node> getRotatedNodes() {
-        return nodes;
-    }
 }
