Ticket #5652: patch
File patch, 22.4 KB (added by , 15 years ago) |
---|
-
core/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
26 26 import org.openstreetmap.josm.command.Command; 27 27 import org.openstreetmap.josm.command.MoveCommand; 28 28 import org.openstreetmap.josm.command.RotateCommand; 29 import org.openstreetmap.josm.command.ScaleCommand; 29 30 import org.openstreetmap.josm.command.SequenceCommand; 30 31 import org.openstreetmap.josm.data.coor.EastNorth; 31 32 import org.openstreetmap.josm.data.osm.DataSet; … … 60 61 public class SelectAction extends MapMode implements SelectionEnded { 61 62 //static private final Logger logger = Logger.getLogger(SelectAction.class.getName()); 62 63 63 enum Mode { move, rotate, s elect }64 enum Mode { move, rotate, scale, select } 64 65 private Mode mode = null; 65 66 private SelectionManager selectionManager; 66 67 … … 78 79 private Cursor oldCursor; 79 80 80 81 /** 81 * The position of the mouse before the user moves a node.82 * The position of the mouse before the user starts to drag it while pressing a button. 82 83 */ 83 private Point mousePos;84 private Point startingDraggingPos; 84 85 85 86 /** 87 * The last known position of the mouse. 88 */ 89 private Point lastMousePos; 90 91 /** 86 92 * The time of the user mouse down event. 87 93 */ 88 94 private long mouseDownTime = 0; … … 105 111 * @param mapFrame The MapFrame this action belongs to. 106 112 */ 107 113 public SelectAction(MapFrame mapFrame) { 108 super(tr("Select"), "move/move", tr("Select, move and rotate objects"),114 super(tr("Select"), "move/move", tr("Select, move, scale and rotate objects"), 109 115 Shortcut.registerShortcut("mapmode:select", tr("Mode: {0}", tr("Select")), KeyEvent.VK_S, Shortcut.GROUP_EDIT), 110 116 mapFrame, 111 117 getCursor("normal", "selection", Cursor.DEFAULT_CURSOR)); … … 169 175 // do not count anything as a move if it lasts less than 100 milliseconds. 170 176 if ((mode == Mode.move) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return; 171 177 172 if(mode != Mode.rotate ) // button is pressed in rotate mode178 if(mode != Mode.rotate && mode != Mode.scale) // button is pressed in rotate mode 173 179 if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) 174 180 return; 175 181 … … 177 183 setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 178 184 } 179 185 186 if ( startingDraggingPos == null ) 187 startingDraggingPos = new Point(e.getX(), e.getY()); 188 180 189 if (!initialMoveThresholdExceeded) { 181 int dxp = mousePos.x - e.getX();182 int dyp = mousePos.y - e.getY();190 int dxp = lastMousePos.x - e.getX(); 191 int dyp = lastMousePos.y - e.getY(); 183 192 int dp = (int) Math.sqrt(dxp*dxp+dyp*dyp); 184 193 if (dp < initialMoveThreshold) return; 185 194 initialMoveThresholdExceeded = true; 186 195 } 187 196 188 EastNorth mouseEN = mv.getEastNorth(e.getX(), e.getY()); 189 EastNorth mouseStartEN = mv.getEastNorth(mousePos.x, mousePos.y); 190 double dx = mouseEN.east() - mouseStartEN.east(); 191 double dy = mouseEN.north() - mouseStartEN.north(); 197 EastNorth currentEN = mv.getEastNorth(e.getX(), e.getY()); 198 EastNorth lastEN = mv.getEastNorth(lastMousePos.x, lastMousePos.y); 199 EastNorth startEN = mv.getEastNorth(startingDraggingPos.x, startingDraggingPos.y); 200 double dx = currentEN.east() - lastEN.east(); 201 double dy = currentEN.north() - lastEN.north(); 192 202 if (dx == 0 && dy == 0) 193 203 return; 194 204 … … 210 220 virtualWays.clear(); 211 221 virtualNode = null; 212 222 } else { 213 // Currently we support moving and rotating,which do not affect relations.223 // Currently we support only transformations which do not affect relations. 214 224 // So don't add them in the first place to make handling easier 215 225 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelectedNodesAndWays(); 216 226 Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection); 217 227 218 // when rotating, having only one node makes no sense - quit silently219 if ( mode == Mode.rotate && affectedNodes.size() < 2)228 // for these transformations, having only one node makes no sense - quit silently 229 if (affectedNodes.size() < 2 && ( mode == Mode.rotate || mode == Mode.scale ) ) 220 230 return; 221 231 222 232 Command c = !Main.main.undoRedo.commands.isEmpty() … … 250 260 } 251 261 } 252 262 } else if (mode == Mode.rotate) { 253 if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).get RotatedNodes())) {254 ((RotateCommand)c). rotateAgain(mouseStartEN, mouseEN);263 if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).getTransformedNodes())) { 264 ((RotateCommand)c).handleEvent(currentEN); 255 265 } else { 256 Main.main.undoRedo.add(new RotateCommand(selection, mouseStartEN, mouseEN));266 Main.main.undoRedo.add(new RotateCommand(selection, currentEN)); 257 267 } 268 } else if (mode == Mode.scale) { 269 if (c instanceof ScaleCommand && affectedNodes.equals(((ScaleCommand)c).getTransformedNodes())) { 270 ((ScaleCommand)c).handleEvent(currentEN); 271 } else { 272 Main.main.undoRedo.add(new ScaleCommand(selection, currentEN)); 273 } 258 274 } 259 275 } 260 276 261 277 mv.repaint(); 262 mousePos = e.getPoint(); 278 if ( mode != Mode.scale ) 279 lastMousePos = e.getPoint(); 263 280 264 281 didMouseDrag = true; 265 282 } … … 267 284 @Override public void mouseMoved(MouseEvent e) { 268 285 // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired. 269 286 // 270 if ((Main.platform instanceof PlatformHookOsx) && mode == Mode.rotate) {287 if ((Main.platform instanceof PlatformHookOsx) && (mode == Mode.rotate || mode == Mode.scale) ) { 271 288 mouseDragged(e); 272 289 } 273 290 } … … 418 435 419 436 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0; 420 437 boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; 438 boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0; 421 439 422 440 // We don't want to change to draw tool if the user tries to (de)select 423 441 // stuff but accidentally clicks in an empty area when selection is empty … … 425 443 didMouseDrag = false; 426 444 initialMoveThresholdExceeded = false; 427 445 mouseDownTime = System.currentTimeMillis(); 428 mousePos = e.getPoint();446 lastMousePos = e.getPoint(); 429 447 430 448 Collection<OsmPrimitive> c = MapView.asColl( 431 449 mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, false)); … … 442 460 // Mode.rotate redraws here 443 461 setCursor(ImageProvider.getCursor("rotate", null)); 444 462 mv.repaint(); 463 } else if (alt && ctrl) { 464 mode = Mode.scale; 465 466 if (getCurrentDataSet().getSelected().isEmpty()) { 467 getCurrentDataSet().setSelected(c); 468 } 469 470 // Mode.select redraws when selectPrims is called 471 // Mode.move redraws when mouseDragged is called 472 // Mode.scale redraws here 473 // TODO: mouse cursor for scaling 474 setCursor(ImageProvider.getCursor("rotate", null)); 475 mv.repaint(); 445 476 } else if (!c.isEmpty()) { 446 477 mode = Mode.move; 447 478 … … 468 499 if(!mv.isActiveLayerVisible()) 469 500 return; 470 501 502 startingDraggingPos = null; 503 471 504 restoreCursor(); 472 505 if (mode == Mode.select) { 473 506 selectionManager.unregister(mv); … … 487 520 488 521 // do nothing if the click was to short to be recognized as a drag, 489 522 // but the release position is farther than 10px away from the press position 490 if ( mousePos.distanceSq(e.getPoint())<100) {523 if (lastMousePos.distanceSq(e.getPoint())<100) { 491 524 selectPrims(cyclePrims(cycleList, e), e, true, false); 492 525 493 526 // If the user double-clicked a node, change to draw mode … … 678 711 return tr("Release the mouse button to stop moving. Ctrl to merge with nearest node."); 679 712 else if (mode == Mode.rotate) 680 713 return tr("Release the mouse button to stop rotating."); 714 else if (mode == Mode.scale) 715 return tr("Release the mouse button to stop scaling."); 681 716 else 682 return tr("Move objects by dragging; Shift to add to selection (Ctrl to toggle); Shift-Ctrl to rotate selected; or change selection");717 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"); 683 718 } 684 719 685 720 @Override public boolean layerIsSupported(Layer l) { -
core/src/org/openstreetmap/josm/command/TransformNodesCommand.java
1 package org.openstreetmap.josm.command; 2 3 import static org.openstreetmap.josm.tools.I18n.trn; 4 5 import java.awt.Point; 6 7 import java.util.Collection; 8 import java.util.HashMap; 9 import java.util.LinkedList; 10 import java.util.Map; 11 12 import javax.swing.JLabel; 13 14 import org.openstreetmap.josm.data.coor.EastNorth; 15 import org.openstreetmap.josm.data.coor.LatLon; 16 import org.openstreetmap.josm.data.osm.Node; 17 import org.openstreetmap.josm.data.osm.OsmPrimitive; 18 import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; 19 import org.openstreetmap.josm.tools.ImageProvider; 20 21 /** 22 * Abstract class with common services for nodes rotation and scaling commands. 23 * 24 * @author Olivier Croquette <ocroquette@free.fr> 25 */ 26 public abstract class TransformNodesCommand extends Command { 27 28 /** 29 * The nodes to transform. 30 */ 31 protected Collection<Node> nodes = new LinkedList<Node>(); 32 33 /** 34 * Small helper for holding the interesting part of the old data state of the 35 * nodes. 36 */ 37 public static class OldState { 38 LatLon latlon; 39 EastNorth eastNorth; 40 boolean modified; 41 } 42 43 /** 44 * List of all old states of the nodes. 45 */ 46 protected Map<Node, OldState> oldStates = new HashMap<Node, OldState>(); 47 48 /** 49 * Stores the state of the nodes before the command. 50 */ 51 protected void storeOldState() { 52 for (Node n : this.nodes) { 53 OldState os = new OldState(); 54 os.latlon = new LatLon(n.getCoor()); 55 os.eastNorth = n.getEastNorth(); 56 os.modified = n.isModified(); 57 oldStates.put(n, os); 58 } 59 } 60 61 62 /** 63 * Creates a TransformNodesObject. 64 * Find out the impacted nodes and store their initial state. 65 */ 66 public TransformNodesCommand(Collection<OsmPrimitive> objects) { 67 this.nodes = AllNodesVisitor.getAllNodes(objects); 68 storeOldState(); 69 } 70 71 /** 72 * Handling of a mouse event (e.g. dragging event). 73 * @param currentEN the current world position of the mouse 74 */ 75 public abstract void handleEvent(EastNorth currentEN); 76 77 /** 78 * Implementation for the nodes transformation. 79 * No parameters are given here, you should handle the user input in handleEvent() 80 * and store it internally. 81 */ 82 protected abstract void transformNodes(); 83 84 /** 85 * Finally apply the transformation of the nodes. 86 * This is called when the user is happy with the current state of the command 87 * and its effects. 88 */ 89 @Override 90 public boolean executeCommand() { 91 transformNodes(); 92 flagNodesAsModified(); 93 return true; 94 } 95 96 /** 97 * Flag all nodes as modified. 98 */ 99 public void flagNodesAsModified() { 100 for (Node n : nodes) { 101 n.setModified(true); 102 } 103 } 104 105 /** 106 * Restore the state of the nodes from the backup. 107 */ 108 @Override 109 public void undoCommand() { 110 for (Node n : nodes) { 111 OldState os = oldStates.get(n); 112 n.setCoor(os.latlon); 113 n.setModified(os.modified); 114 } 115 } 116 117 @Override 118 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 119 for (OsmPrimitive osm : nodes) { 120 modified.add(osm); 121 } 122 } 123 124 @Override 125 public JLabel getDescription() { 126 return new JLabel(trn("Transform {0} node", "Transform {0} nodes", nodes.size(), nodes.size()), ImageProvider.get("data", "node"), JLabel.HORIZONTAL); 127 } 128 129 /** 130 * Get the nodes with the current transformation applied. 131 */ 132 public Collection<Node> getTransformedNodes() { 133 return nodes; 134 } 135 136 /** 137 * Get the center of the nodes under modification. 138 * It's just the barycenter. 139 */ 140 public EastNorth getNodesCenter() { 141 EastNorth sum = new EastNorth(0,0); 142 143 for (Node n : nodes ) { 144 EastNorth en = n.getEastNorth(); 145 sum = sum.add(en.east(), en.north()); 146 } 147 return new EastNorth(sum.east()/this.nodes.size(), sum.north()/this.nodes.size()); 148 149 } 150 } -
core/src/org/openstreetmap/josm/command/ScaleCommand.java
1 package org.openstreetmap.josm.command; 2 3 import static org.openstreetmap.josm.tools.I18n.trn; 4 5 import java.util.Collection; 6 7 import javax.swing.JLabel; 8 9 import org.openstreetmap.josm.data.coor.EastNorth; 10 import org.openstreetmap.josm.data.osm.Node; 11 import org.openstreetmap.josm.data.osm.OsmPrimitive; 12 import org.openstreetmap.josm.tools.ImageProvider; 13 14 public class ScaleCommand extends TransformNodesCommand { 15 /** 16 * Pivot point 17 */ 18 private EastNorth pivot; 19 20 /** 21 * Current scaling factor applied 22 */ 23 private double scalingFactor; 24 25 /** 26 * World position of the mouse when the user started the command. 27 * 28 */ 29 EastNorth startEN = null; 30 31 /** 32 * Creates a ScaleCommand. 33 * Assign the initial object set, compute pivot point. 34 * Computation of pivot point is done by the same rules that are used in 35 * the "align nodes in circle" action. 36 */ 37 public ScaleCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) { 38 super(objects); 39 40 pivot = getNodesCenter(); 41 42 // We remember the very first position of the mouse for this action. 43 // Note that SelectAction will keep the same ScaleCommand when the user 44 // releases the button and presses it again with the same modifiers. 45 // The very first point of this operation is stored here. 46 startEN = currentEN; 47 48 handleEvent(currentEN); 49 } 50 51 /** 52 * Compute new scaling factor and transform nodes accordingly. 53 */ 54 @Override 55 public void handleEvent(EastNorth currentEN) { 56 double startAngle = Math.atan2(startEN.east()-pivot.east(), startEN.north()-pivot.north()); 57 double endAngle = Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north()); 58 double startDistance = pivot.distance(startEN); 59 double currentDistance = pivot.distance(currentEN); 60 scalingFactor = Math.cos(startAngle-endAngle) * currentDistance / startDistance; 61 transformNodes(); 62 } 63 64 65 /** 66 * Scale nodes. 67 */ 68 @Override 69 protected void transformNodes() { 70 // scalingFactor = 2.0; 71 for (Node n : nodes) { 72 EastNorth oldEastNorth = oldStates.get(n).eastNorth; 73 double dx = oldEastNorth.east() - pivot.east(); 74 double dy = oldEastNorth.north() - pivot.north(); 75 double nx = pivot.east() + scalingFactor * dx; 76 double ny = pivot.north() + scalingFactor * dy; 77 n.setEastNorth(new EastNorth(nx, ny)); 78 } 79 } 80 81 @Override public JLabel getDescription() { 82 return new JLabel(trn("Scale {0} node", "Scale {0} nodes", nodes.size(), nodes.size()), ImageProvider.get("data", "node"), JLabel.HORIZONTAL); 83 } 84 85 } -
core/src/org/openstreetmap/josm/command/RotateCommand.java
19 19 /** 20 20 * RotateCommand rotates a number of objects around their centre. 21 21 * 22 * @author Frederik Ramm <frederik@remote.org>23 22 */ 24 public class RotateCommand extends Command {23 public class RotateCommand extends TransformNodesCommand { 25 24 26 25 /** 27 * The objects to rotate.26 * Pivot point 28 27 */ 29 private Collection<Node> nodes = new LinkedList<Node>();30 31 /**32 * pivot point33 */34 28 private EastNorth pivot; 35 29 36 /** 37 * Small helper for holding the interesting part of the old data state of the 38 * objects. 39 */ 40 public static class OldState { 41 LatLon latlon; 42 EastNorth eastNorth; 43 boolean modified; 44 } 30 /** 31 * World position of the mouse when the user started the command. 32 * 33 */ 34 EastNorth startEN = null; 45 35 46 36 /** 47 37 * angle of rotation starting click to pivot 48 38 */ 49 private double startAngle ;39 private double startAngle = 0.0; 50 40 51 41 /** 52 42 * computed rotation angle between starting click and current mouse pos 53 43 */ 54 private double rotationAngle ;44 private double rotationAngle = 0.0; 55 45 56 46 /** 57 * List of all old states of the objects.58 */59 private Map<Node, OldState> oldState = new HashMap<Node, OldState>();60 61 /**62 47 * Creates a RotateCommand. 63 * Assign the initial object set, compute pivot point and rotation angle. 64 * Computation of pivot point is done by the same rules that are used in 65 * the "align nodes in circle" action. 48 * Assign the initial object set, compute pivot point and inital rotation angle. 66 49 */ 67 public RotateCommand(Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) { 50 public RotateCommand(Collection<OsmPrimitive> objects, EastNorth currentEN) { 51 super(objects); 68 52 69 this.nodes = AllNodesVisitor.getAllNodes(objects); 70 pivot = new EastNorth(0,0); 53 pivot = getNodesCenter(); 71 54 72 for (Node n : this.nodes) { 73 OldState os = new OldState(); 74 os.latlon = new LatLon(n.getCoor()); 75 os.eastNorth = n.getEastNorth(); 76 os.modified = n.isModified(); 77 oldState.put(n, os); 78 pivot = pivot.add(os.eastNorth.east(), os.eastNorth.north()); 79 } 80 pivot = new EastNorth(pivot.east()/this.nodes.size(), pivot.north()/this.nodes.size()); 55 // We remember the very first position of the mouse for this action. 56 // Note that SelectAction will keep the same ScaleCommand when the user 57 // releases the button and presses it again with the same modifiers. 58 // The very first point of this operation is stored here. 59 startEN = currentEN; 81 60 82 rotationAngle = Math.PI/2; 83 rotateAgain(start, end); 84 } 61 startAngle = getAngle(currentEN); 62 rotationAngle = 0.0; 85 63 64 handleEvent(currentEN); 65 } 66 86 67 /** 87 * Rotate the same set of objects again, by the angle between given 88 * start and end nodes. Internally this is added to the existing 89 * rotation so a later undo will undo the whole rotation. 90 */ 91 public void rotateAgain(EastNorth start, EastNorth end) { 92 // compute angle 93 startAngle = Math.atan2(start.east()-pivot.east(), start.north()-pivot.north()); 94 double endAngle = Math.atan2(end.east()-pivot.east(), end.north()-pivot.north()); 95 rotationAngle += startAngle - endAngle; 96 rotateNodes(false); 68 * Get angle between the horizontal axis and the line formed by the pivot and give points. 69 **/ 70 protected double getAngle(EastNorth currentEN) { 71 if ( pivot == null ) 72 return 0.0; // should never happen by contract 73 return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north()); 97 74 } 98 75 99 /** 100 * Helper for actually rotationg the nodes. 101 * @param setModified - true if rotated nodes should be flagged "modified" 102 */ 103 private void rotateNodes(boolean setModified) { 76 /** 77 * Compute new rotation angle and transform nodes accordingly. 78 */ 79 @Override 80 public void handleEvent(EastNorth currentEN) { 81 double currentAngle = getAngle(currentEN); 82 rotationAngle = currentAngle - startAngle; 83 transformNodes(); 84 } 85 86 /** 87 * Rotate nodes. 88 */ 89 @Override 90 protected void transformNodes() { 104 91 for (Node n : nodes) { 105 92 double cosPhi = Math.cos(rotationAngle); 106 93 double sinPhi = Math.sin(rotationAngle); 107 EastNorth oldEastNorth = oldState .get(n).eastNorth;94 EastNorth oldEastNorth = oldStates.get(n).eastNorth; 108 95 double x = oldEastNorth.east() - pivot.east(); 109 96 double y = oldEastNorth.north() - pivot.north(); 110 double nx = sinPhi * x + cosPhi * y + pivot.east();111 double ny = - cosPhi * x + sinPhi * y + pivot.north();97 double nx = cosPhi * x + sinPhi * y + pivot.east(); 98 double ny = -sinPhi * x + cosPhi * y + pivot.north(); 112 99 n.setEastNorth(new EastNorth(nx, ny)); 113 if (setModified) {114 n.setModified(true);115 }116 100 } 117 101 } 118 102 119 @Override public boolean executeCommand() { 120 rotateNodes(true); 121 return true; 122 } 123 124 @Override public void undoCommand() { 125 for (Node n : nodes) { 126 OldState os = oldState.get(n); 127 n.setCoor(os.latlon); 128 n.setModified(os.modified); 129 } 130 } 131 132 @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 133 for (OsmPrimitive osm : nodes) { 134 modified.add(osm); 135 } 136 } 137 138 @Override public JLabel getDescription() { 103 @Override 104 public JLabel getDescription() { 139 105 return new JLabel(trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size()), ImageProvider.get("data", "node"), JLabel.HORIZONTAL); 140 106 } 141 107 142 public Collection<Node> getRotatedNodes() {143 return nodes;144 }145 108 }